诸如将 2 个浮点变量相加之类的技术只是有效地将尾数位宽加倍,因此足以存储/加载更大的尾数。
标准IEEE 754 双具有 52+1 位尾数,导致
log10(2^53) = 15.95 = ~16 [dec digits]
所以当你添加2个这样的变量时:
log10(2^(53+53)) = 31.9 = ~32 [dec digits]
所以只需将 32 位尾数存储/加载到/从字符串中。2 个变量的指数将相差 +/- 53,因此足以存储其中一个。
为了进一步提高性能和精度,您可以使用十六进制字符串。它的速度要快得多,而且没有四舍五入,因为您可以直接在尾数位和十六进制字符串字符之间进行转换。
任何 4 位形成一个十六进制数字,所以
(53+53) / 4 = 26.5 = ~27 [hex digits]
正如您所看到的,它的存储效率也更高,唯一的问题是指数分隔符作为十六进制数字包含E
,因此您需要通过大写/小写区分数字和指数分隔符或使用不同的字符或仅使用符号,例如:
1.23456789ABCDEFe10
1.23456789ABCDEFe+10
1.23456789ABCDEF|+10
1.23456789ABCDEF+10
我通常使用第一个版本。您还需要记住指数是尾数的位移,因此结果数字是:
mantisa<<exponent = mantisa * (2^exponent)
现在,在从/到字符串加载/存储期间,您只需加载53+53
位整数,然后将其分隔为 2 个尾数并在位级别重建浮点值......重要的是你的尾数对齐所以exp1+53 = exp2
给或取1
...
所有这些都可以在整数算术上完成。
如果您的指数是 exp10,那么您将在存储和加载到/从字符串期间对数字进行大量舍入,因为您的尾数通常会在小数点之前或之后丢失许多零位,这使得十进制和二进制/十六进制之间的转换非常困难和不准确(特别是如果您将计算限制64/80/128/160 bits
为尾数)。
这是一个C++ 示例(仅在整数算术上以十进制打印 32 位浮点数):
//---------------------------------------------------------------------------
AnsiString f32_prn(float fx) // scientific format integers only
{
const int ms=10+5; // mantisa digits
const int es=2; // exponent digits
const int eb=100000;// 10^(es+3)
const int sz=ms+es+5;
char txt[sz],c;
int i=0,i0,i1,m,n,exp,e2,e10;
DWORD x,y,man;
for (i0=0;i0<sz;i0++) txt[i0]=' ';
// float -> DWORD
x=((DWORD*)(&fx))[0];
// sign
if (x>=0x80000000){ txt[i]='-'; i++; x&=0x7FFFFFFF; }
else { txt[i]='+'; i++; }
// exp
exp=((x>>23)&255)-127;
// man
man=x&0x007FFFFF;
if ((exp!=-127)&&(exp!=+128)) man|=0x00800000; // not zero or denormalized or Inf/NaN
// special cases
if ((man==0)&&(exp==-127)){ txt[i]='0'; i++; txt[i]=0; return txt; } // +/- zero
if ((man==0)&&(exp==+128)){ txt[i]='I'; i++;
txt[i]='N'; i++;
txt[i]='F'; i++; txt[i]=0; return txt; } // +/- Infinity
if ((man!=0)&&(exp==+128)){ txt[i]='N'; i++;
txt[i]='A'; i++;
txt[i]='N'; i++; txt[i]=0; return txt; } // +/- Not a number
// align man,exp to 4bit
e2=(1+(exp&3))&3;
man<<=e2;
exp-=e2+23; // exp of lsb of mantisa
e10=0; // decimal digits to add/remove
m=0; // mantisa digits
n=ms; // max mantisa digits
// integer part
if (exp>=-28)
{
x=man; y=0; e2=exp;
// shift x to integer part <<
if (x) for (;e2>0;)
{
while (x>0x0FFFFFFF){ y/=10; y+=((x%10)<<28)/10; x/=10; e10++; }
e2-=4; x<<=4; y<<=4;
x+=(y>>28)&15; y&=0x0FFFFFFF;
}
// shift x to integer part >>
for (;e2<0;e2+=4) x>>=4;
// no exponent?
if ((e10>0)&&(e10<=es+3)) n++; // no '.'
// print
for (i0=i;x;)
{
if (m<n){ txt[i]='0'+(x%10); i++; m++; if ((m==n)&&(x<eb)) m+=es+1; } else e10++;
x/=10;
}
// reverse digits
for (i1=i-1;i0<i1;i0++,i1--){ c=txt[i0]; txt[i0]=txt[i1]; txt[i1]=c; }
}
// fractional part
if (exp<0)
{
x=man; y=0; e2=exp;
// shift x to fractional part <<
if (x) for (;e2<-28;)
{
while ((x<=0x19999999)&&(y<=0x19999999)){ y*=10; x*=10; x+=(y>>28)&15; y&=0x0FFFFFFF; e10--; }
y>>=4; y&=0x00FFFFFF; y|=(x&15)<<24;
x>>=4; x&=0x0FFFFFFF; e2+=4;
}
// shift x to fractional part <<
for (;e2>-28;e2-=4) x<<=4;
// print
x&=0x0FFFFFFF;
if ((m)&&(!e10)) n+=es+2; // no exponent means more digits for mantisa
if (x)
{
if (m){ txt[i]='.'; i++; }
for (i0=i;x;)
{
y*=10; x*=10;
x+=(y>>28)&15;
if (m<n)
{
i0=((x>>28)&15);
if (!m)
{
if (i0)
{
txt[i]='0'+i0; i++; m++;
txt[i]='.'; i++;
}
e10--;
if (!e10) n+=es+2; // no exponent means more digits for mantisa
}
else { txt[i]='0'+i0; i++; m++; }
} else break;
y&=0x0FFFFFFF;
x&=0x0FFFFFFF;
}
}
}
else{
// no fractional part
if ((e10>0)&&(e10<sz-i))
for (;e10;e10--){ txt[i]='0'+i0; i++; m++; }
}
// exponent
if (e10)
{
if (e10>0) // move . after first digit
{
for (i0=i;i0>2;i0--) txt[i0]=txt[i0-1];
txt[2]='.'; i++; e10+=i-3;
}
// sign
txt[i]='E'; i++;
if (e10<0.0){ txt[i]='-'; i++; e10=-e10; }
else { txt[i]='+'; i++; }
// print
for (i0=i;e10;){ txt[i]='0'+(e10%10); e10/=10; i++; }
// reverse digits
for (i1=i-1;i0<i1;i0++,i1--){ c=txt[i0]; txt[i0]=txt[i1]; txt[i1]=c; }
}
txt[i]=0;
return txt;
}
//---------------------------------------------------------------------------
只需将AnsiString
返回类型更改为任何字符串类型,或者char*
您可以随意使用...
正如您所看到的,它的大量代码带有很多 hack,并且在内部使用了超过 24 位的尾数来降低十进制指数造成的舍入误差。
exp2
所以我强烈建议对尾数使用二进制指数(唯一的问题是当你想打印或输入十进制数时,在这种情况下你别无选择,只能四舍五入......幸运的是,你可以使用六进制输出并将其转换为字符串上的十进制......或者从单变量 prints 构造打印。 ..
有关更多信息,请参阅相关 QA: