我不是 JAVASCRIPT 编码员,所以我坚持使用 C++ ...
在十进制基数中将数字转换为字符串比使用二进制或其幂基数(bin、oct、hex)更复杂,因为普通计算机上的所有数字都以二进制而不是十年。如果您转换整数或小数部分,它也不相同。假设我们有数字 x 并且想要在 ASCII 中编码的字符串 s,所以这就是基本转换的工作原理:
-
处理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的整数部分
整数通过除被打印基数转换为字符串,所以:
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 循环反转字符串中的数字,或者您可以直接以相反的顺序存储数字。但是对于 tat,您需要知道数字的整数部分的位数。这是由
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 编辑器中对所有代码进行了编码,因此可能存在隐藏的语法错误。
现在通常的打印函数也具有添加零或空格填充或截断高于某个值等的小数位的格式...
如果你有一个 bignum x,那么这会慢得多,因为你不能像 O(1) 那样处理基本的 +,-,*,/ 操作,而且创建 hex 字符串并将字符串转换为 decadic on 通常更快8位算术或使用适合存储bignum的已用DATA WORD的10的最大幂。 hex -> dec 转换可以这样完成:
但是对于非常大的字符串,它会很慢。在这种情况下,可以通过使用类似于 Schönhage-Strassen 乘法 的 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 常量是触发舍入的后续 0 或 9 的阈值。