Я не JAVASCRIPT кодер, поэтому я придерживаюсь C ++ ...
Преобразование числа в строку в десятичное основание большеусложняется использование двоичного кода или его базовых степеней (bin, oct, hex) из-за того, что все числа на обычном компьютере хранятся в двоичном, а не в десятичном виде.Также это не то же самое, если вы конвертируете целую или дробную часть.Предположим, что у нас есть номер x
и мы хотим, чтобы строка s
была закодирована в ASCII , так как работает базовое преобразование:
Дескриптор 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, или вы можете сохранить цифры в обратном порядке напрямую.Но для тата нужно знать количество цифр целой части числа.Это делается с помощью
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
и преобразовывать строку в десятичную на 8-битной арифметике или использовать большую степень 10, которая подходитв используемом СЛОВЕ ДАННЫХ Bignum хранится с.hex -> dec
преобразование может быть сделано следующим образом:
, но опять же для очень больших строк это будетмедленный.В этом случае его можно ускорить, используя FFT / NTT подходы, подобные умножению Schönhage-Strassen , но я никогда раньше не пытался использовать его для печати, поэтому мне не хватает понимания такого подхода.
Также имейте в виду, что определение количества цифр значения не является регулярным для дробной части числа (см. Ссылку выше), поэтому вам следует иметь в виду, что вас могут отключить цифры 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
является пороговым значением количества последовательных нулей или девяток, запускающих округление.