我不是一个脚本语言编码器所以我坚持C++ ...
将数字转换为字符串十年基数比使用二进制或其幂基(bin,oct,hex)更复杂,因为普通计算机上的所有数字都以二进制存储而不是十进制存储。如果转换整数或小数部分,情况也不一样。假设我们有数字x
并想要字符串s
编码在ASCII http://www.asciitable.com这就是基本转换的工作原理:
-
Handle sign
s="+";
if (x<0.0) { x=-x; s="-"; }
正如你所看到的,这很容易。某些数字格式具有单独的符号位(通常是 msb),因此在这种情况下,代码可以转换为位运算,例如 32 位float
:
DWORD* dw=(DWORD*)(&x); // allow bit manipulation
s="+";
s[0]+=(((*dw)>>30)&2); // ASCII +,- codes are 2 apart
(*dw)&=0x7FFFFFFF; // x=abs(x)
所以我们已经为字符串提取了符号字符并制作x
未签名。
-
处理整数部分x
整数转换为字符串dividing它由印刷基地所以:
y=floor(x); // integer part
if (y)
for (;y;) // until number is nonzero
{
s+='0'+(y%10); // works only for up to 10 base
y/=10;
}
else s+='0'; // handle y=0 separately
因此每个除法的余数就是字符串所需的数字,但顺序相反。因此,在转换后,通过单个 for 循环反转字符串中的数字,或者您可以直接以相反的顺序存储数字。但为此,您需要知道该数字的整数部分的位数。这是由
digits = ceil(log(y)/log(base)) + 1
所以对于十年:
digits = ceil(log10(y)) + 1
-
处理小数部分x
这是通过转换相乘通过转换基数。
z=x-floor(x); // fractional part
if (z)
for (s+='.';z;) // until number is nonzero here you can limit to number of digits
{
z*=10.0;
s+='0'+int(floor(z)); // works only for up to 10 base
z-=floor(z);
}
这是按顺序返回数字,所以这次没有反转......
我直接在 SO 编辑器中对所有代码进行编码,因此可能存在隐藏的语法错误。
现在,通常的打印功能还具有格式,可以添加零或空格填充或截断某个值以上的小数位等...
如果你有一个大数x
那么这会慢得多,因为你无法处理基本的+,-,*,/
操作为O(1)
不再这样了,创建速度通常更快hex
string 并使用 8 位算术将字符串转换为十进制,或者使用适合存储 bignum 的已用数据字的 10 的最大幂。这hex -> dec
转换可以这样完成:
- 整数数学上的字符串十六进制到十进制转换 https://stackoverflow.com/a/18231860/2521214
但同样,对于非常大的字符串,它会很慢。在这种情况下,可以通过使用来加速FFT/NTT方法类似于舍恩哈格-施特拉森乘法但我之前从未尝试过使用它进行打印,所以我对这种方法缺乏任何见解。
另请注意,对于数字的小数部分,确定值的位数并不规则(请参阅上面的链接),因此您需要注意,您可能会通过1-2
数字。
[Edit1] 对字符串进行四舍五入
只要你检测到n
小数部分中的后续零或九(在任何非零数字之后)您需要停止打印并舍入。零被切掉,九也需要被切掉,并将字符串中的其余部分加一。此类操作可能会溢出到字符串中不存在的 1 位数字,因此在这种情况下只需插入1
.
当我把所有这些放在一起时,我想出了这个C++/VCL代码(基于VCL AnsiString
数据类型):
AnsiString print(double x)
{
char c;
int i,j;
double y,a;
AnsiString s;
const int B=10; // chose base 2...16
const double b=B; // base
const double _b=1.0/b; // 1/base
const char digit[16]="0123456789ABCDEF";
#define _enable_rounding
#ifdef _enable_rounding
const int round_digits=5; // min consequent 0s or B-1s to triger rounding
int cnt0=0,cnt1=0; // consequent digit counters
int ena=0; // enabled consequent digit counters? after first nonzero digit
#endif
// here you should handle NaN and Inf cases
// handle sign
s="+";
if (x<0.0) { x=-x; s="-"; }
// integer part
y=floor(x);
if (y) for (;y>0.0;) // until number is nonzero
{
a=y; y=floor(y*_b); // the same as y/=10 on integers
a-=y*b; // the same as a=y%10 on integers
i=int(a);
s+=digit[i];
#ifdef _enable_rounding
ena|=i;
#endif
}
else s+='0'; // handle y=0 separately
// reverse string skipping +/- sign (beware AnsiString is indexed from 1 up to its length included!!!)
for (i=2,j=s.Length();i<j;i++,j--){ c=s[i]; s[i]=s[j]; s[j]=c; }
// fractional part
y=x-floor(x);
if (y) for (s+='.';y>0.0;) // until number is nonzero here you can limit to number of digits
{
y*=b;
a=floor(y);
y-=a;
i=int(a);
s+=digit[i];
#ifdef _enable_rounding
ena|=i;
// detect consequent rounding digits
if (ena)
{
if (i== 0){ cnt0++; cnt1=0; }
else if (i==B-1){ cnt1++; cnt0=0; }
else { cnt0=0; cnt1=0; }
}
// round down .???00000000 by cut of zeros
if (cnt0>=round_digits)
{
s=s.SubString(1,s.Length()-cnt0); // by cut of zeros
break;
}
// round up .???999999999 by increment and cut of zeros (only base 10) !!!
if (cnt1>=round_digits)
{
s=s.SubString(1,s.Length()-cnt1); // cut off nines
for (j=1,i=s.Length();(i>=2)&&(j);i--)
{
c=s[i];
if (c=='.') continue;
if (c=='9'){ s[i]='0'; continue; }
j=0; s[i]++;
}
if (j) s=s.Insert("1",i+1); // overflow -> insert "1" after sign
if (s[s.Length()]=='.') // cut off decimal point if no fractional part left
s=s.SubString(1,s.Length()-1);
break;
}
#endif
}
return s;
}
您可以选择基地B=<2,16>
。您可以通过使用/注释来启用禁用舍入#define _enable_rounding
。请注意,舍入例程仅适用于基数10
对于不同的基数,增量例程会有一些不同的代码/常量,并且懒得通用(它会更长且更难以理解的代码)。这round_digits
Constant 是触发舍入的后续零或九的阈值。