C动态printf double,不会丢失精度,也不会有尾随零

我是C的新手,从网上学习/上网。 我正在尝试编写一个函数,我可以传递任何double并返回一个int ,用于printf("%.*lf" ...语句,这样返回的int既不会降低精度也不会产生尾随零。

我有一个工作函数,但它很大,因为它是为了可读性而编写的,所有注释。

为了总结这个函数,我计算在10 > d >= 0的范围内得到double所需的除以10的除法,只取小数部分并将其放入一个带有n个小数位的string ,其中n = 15 - number_of_digits_left_of_decimal (我读到double类型只能跟踪15位数,从右到左检查string是否为尾随零并保持计数,最后返回一个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  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-bit IEEE-754浮点格式为尾数保留53位,相当于floor(log10(2 ^ 53)) = 15位十进制数字,这种格式的有效数字可能需要多达一些精确打印时,小数部分中包含1080十进制数字,这就是您要求的内容。

解决此问题的一种方法是使用snprintf()%a格式类型说明符,它将使用hex数字作为尾数打印浮点值,而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  #include  #include  #include  #include  #include  #include  #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,4, 2,4,3,4, 1,4,3,4, 2,4,3,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, 1, // 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], testData[i], digits, testData[i]); } return 0; } 

输出( ideone ):

 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. 

您可以看到π0.1仅为15位十进制数字,其余数字显示数字实际舍入到的数字,因为这些数字无法以二进制浮点格式精确表示。

您还可以看到DBL_MIN是最小的正标准化double DBL_MIN值,在小数部分中有1022位数,有715位有效数字。

此解决方案的可能问题:

  • 编译器的printf()函数不支持%a或者没有正确打印精度请求的所有数字(这很可能)。
  • 您的计算机使用非二进制浮点格式(这是非常罕见的)。

我注意到的第一件事是你将temp除以10会导致精度下降。

不要让你失望或阻止你再次尝试,但正确实施这一点比你所展示的要多得多。

Guy L. Steele和Jon L. White撰写了一篇名为“ 如何精确打印浮点数 ”的论文,详细介绍了一些陷阱,并提出了一种打印浮点数的工作算法。 这是一个很好的阅读。