我有一个工作函数,但它很大,因为它是为了可读性而编写的,所有注释都是如此.
为了总结这个功能,我计算在10>范围内获得双倍所需的分数乘以10. d> = 0,只取小数部分并将其放入一个带有n个小数位的字符串,其中n = 15 – number_of_digits_left_of_decimal(我读过那个类型double只能跟踪15位数),从右到左检查字符串是否为尾随零并保持计数,最后返回一个int,表示小数右边的非零数字.
有没有更简单的方法?谢谢.
int get_number_of_digits_after_decimal(double d) { int i = 0; /* sometimes you need an int */ int pl = 0; /* precision left = 15 - sigfigs */ int sigfigs = 1; /* the number of digits in d */ char line[20]; /* used to find last non-zero digit right of the decimal place */ double temp; /* a copy of d used for destructive calculations */ /* find digits to right of decimal */ temp = d; while(sigfigs < 15) { if(temp < 0) temp *= -1; if(temp < 10) break; temp /= 10; ++sigfigs; } /* at this point 10 > temp >= 0 * decrement temp unitl 1 > temp >=0 */ while(temp > 1) { --temp; } if(temp == 0) return(0); pl = 15 - sigfigs; /* if n digits left of decimal,15-n to right */ switch(pl) { case 14: sprintf(line,"%.14lf",d); break; case 13: sprintf(line,"%.13lf",d); break; case 12: sprintf(line,"%.12lf",d); break; case 11: sprintf(line,"%.11lf",d); break; case 10: sprintf(line,"%.10lf",d); break; case 9: sprintf(line,"%.9f",d); break; case 8: sprintf(line,"%.8lf",d); break; case 7: sprintf(line,"%.7lf",d); break; case 6: sprintf(line,"%.6lf",d); break; case 5: sprintf(line,"%.5lf",d); break; case 4: sprintf(line,"%.4lf",d); break; case 3: sprintf(line,"%.3lf",d); break; case 2: sprintf(line,"%.2lf",d); break; case 1: sprintf(line,"%.1lf",d); break; case 0: return(0); break; } i = (strlen(line) - 1); /* last meaningful digit char */ while(1) /* start at end of string,move left checking for first non-zero */ { if(line[i] == '0') /* if 0 at end */ { --i; --pl; } else { break; } } return(pl); }
解决方法
>浮点运算的大多数实际实现都不是十进制的,它们是二进制的.因此,当您将浮点数乘以10或除以10时,您可能会失去精度(这取决于数字).
>即使标准的64位IEEE-754浮点格式为尾数保留53位,相当于floor(log10(2 ^ 53))= 15位十进制数字,此格式的有效数字可能需要最多精确打印时,小数部分中有1080个十进制数字,这就是您要求的内容.
解决这个问题的一种方法是使用snprintf()中的%a格式类型说明符,它将使用十六进制数字作为尾数打印浮点值,而1999年的C标准保证这将打印所有有效数字,如果浮点格式为radix-2(AKA base-2或简称二进制).因此,通过这个,您可以获得该数字的尾数的所有二进制数字.从这里你可以计算出小数部分中有多少个十进制数字.
现在,观察:
1.00000 = 2 0 = 1.00000(二进制)
0.50000 = 2-1 = 0.10000
0.25000 = 2-2 = 0.01000
0.12500 = 2-3 = 0.00100
0.06250 = 2-4 = 0.00010
0.03125 = 2-5 = 0.00001
等等.
您可以清楚地看到,二进制表示中该点右侧第i个位置的二进制数字也会产生最后一个非零十进制数字,也位于十进制表示中该点右侧的第i个位置.
因此,如果您知道最低有效非零位在二进制浮点数中的位置,则可以确定需要多少个十进制数来精确打印数字的小数部分.
这就是我的计划正在做的事情.
码:
// file: PrintFullFraction.c // // compile with gcc 4.6.2 or better: // gcc -Wall -Wextra -std=c99 -O2 PrintFullFraction.c -o PrintFullFraction.exe #include <limits.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h> #include <float.h> #include <assert.h> #if FLT_RADIX != 2 #error currently supported only FLT_RADIX = 2 #endif int FractionalDigits(double d) { char buf[ 1 + // sign,'-' or '+' (sizeof(d) * CHAR_BIT + 3) / 4 + // mantissa hex digits max 1 + // decimal point,'.' 1 + // mantissa-exponent separator,'p' 1 + // mantissa sign,'-' or '+' (sizeof(d) * CHAR_BIT + 2) / 3 + // exponent decimal digits max 1 // string terminator,'\0' ]; int n; char *pp,*p; int e,lsbFound,lsbPos; // convert d into "+/- 0x h.hhhh p +/- ddd" representation and check for errors if ((n = snprintf(buf,sizeof(buf),"%+a",d)) < 0 || (unsigned)n >= sizeof(buf)) return -1; //printf("{%s}",buf); // make sure the conversion didn't produce something like "nan" or "inf" // instead of "+/- 0x h.hhhh p +/- ddd" if (strstr(buf,"0x") != buf + 1 || (pp = strchr(buf,'p')) == NULL) return 0; // extract the base-2 exponent manually,checking for overflows e = 0; p = pp + 1 + (pp[1] == '-' || pp[1] == '+'); // skip the exponent sign at first for (; *p != '\0'; p++) { if (e > INT_MAX / 10) return -2; e *= 10; if (e > INT_MAX - (*p - '0')) return -2; e += *p - '0'; } if (pp[1] == '-') // apply the sign to the exponent e = -e; //printf("[%s|%d]",buf,e); // find the position of the least significant non-zero bit lsbFound = lsbPos = 0; for (p = pp - 1; *p != 'x'; p--) { if (*p == '.') continue; if (!lsbFound) { int hdigit = (*p >= 'a') ? (*p - 'a' + 10) : (*p - '0'); // assuming ASCII chars if (hdigit) { static const int lsbPosInNibble[16] = { 0,4,3,2,1,4 }; lsbFound = 1; lsbPos = -lsbPosInNibble[hdigit]; } } else { lsbPos -= 4; } } lsbPos += 4; if (!lsbFound) return 0; // d is 0 (integer) // adjust the least significant non-zero bit position // by the base-2 exponent (just add them),checking // for overflows if (lsbPos >= 0 && e >= 0) return 0; // lsbPos + e >= 0,d is integer if (lsbPos < 0 && e < 0) if (lsbPos < INT_MIN - e) return -2; // d isn't integer and needs too many fractional digits if ((lsbPos += e) >= 0) return 0; // d is integer if (lsbPos == INT_MIN && -INT_MAX != INT_MIN) return -2; // d isn't integer and needs too many fractional digits return -lsbPos; } const double testData[] = { 0,// 2 ^ 0 0.5,// 2 ^ -1 0.25,// 2 ^ -2 0.125,0.0625,// ... 0.03125,0.015625,0.0078125,// 2 ^ -7 1.0/256,// 2 ^ -8 1.0/256/256,// 2 ^ -16 1.0/256/256/256,// 2 ^ -24 1.0/256/256/256/256,// 2 ^ -32 1.0/256/256/256/256/256/256/256/256,// 2 ^ -64 3.14159265358979323846264338327950288419716939937510582097494459,0.1,INFINITY,#ifdef NAN NAN,#endif DBL_MIN }; int main(void) { unsigned i; for (i = 0; i < sizeof(testData) / sizeof(testData[0]); i++) { int digits = FractionalDigits(testData[i]); assert(digits >= 0); printf("%f %e %.*f\n",testData[i],digits,testData[i]); } return 0; }
0.000000 0.000000e+00 0 1.000000 1.000000e+00 1 0.500000 5.000000e-01 0.5 0.250000 2.500000e-01 0.25 0.125000 1.250000e-01 0.125 0.062500 6.250000e-02 0.0625 0.031250 3.125000e-02 0.03125 0.015625 1.562500e-02 0.015625 0.007812 7.812500e-03 0.0078125 0.003906 3.906250e-03 0.00390625 0.000015 1.525879e-05 0.0000152587890625 0.000000 5.960464e-08 0.000000059604644775390625 0.000000 2.328306e-10 0.00000000023283064365386962890625 0.000000 5.421011e-20 0.0000000000000000000542101086242752217003726400434970855712890625 3.141593 3.141593e+00 3.141592653589793115997963468544185161590576171875 0.100000 1.000000e-01 0.1000000000000000055511151231257827021181583404541015625 inf inf inf nan nan nan 0.000000 2.225074e-308 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201383090232717332404064219215980462331830553327416887204434813918195854283159012511020564067339731035811005152434161553460108856012385377718821130777993532002330479610147442583636071921565046942503734208375250806650616658158948720491179968591639648500635908770118304874799780887753749949451580451605050915399856582470818645113537935804992115981085766051992433352114352390148795699609591288891602992641511063466313393663477586513029371762047325631781485664350872122828637642044846811407613911477062801689853244110024161447421618567166150540154285084716752901903161322778896729707373123334086988983175067838846926092773977972858659654941091369095406136467568702398678315290680984617210924625396728515625
您可以看到π和0.1仅为15位十进制数字,其余数字显示数字实际舍入到的数字,因为这些数字无法以二进制浮点格式精确表示.
您还可以看到DBL_MIN是最小的正标准化双精度值,在小数部分中有1022位数,有715位有效数字.
此解决方案的可能问题:
>您的编译器的printf()函数不支持%a或者没有正确打印精度请求的所有数字(这很可能).>您的计算机使用非二进制浮点格式(这种情况非常罕见).