C动态打印F双精度,无精度损失,无尾随零
我是C语言的新手,从书中/从网上学习。我正在尝试编写一个函数,可以将任何C动态打印F双精度,无精度损失,无尾随零,c,floating-point,printf,C,Floating Point,Printf,我是C语言的新手,从书中/从网上学习。我正在尝试编写一个函数,可以将任何double传递给它,并返回一个int,用于printf(“%.*lf”…语句中,这样返回的int既不会降低精度,也不会产生尾随零 我有一个工作的功能,但它是相当大的,因为它是为可读性和所有评论写的 为了总结这个函数,我计算了在10>d>=0范围内获得double所需的10个除法,只取小数部分并将其放入一个字符串中,其中有n个小数位,其中n=15-小数位数\u(我读到类型double只能跟踪15位数字),从右到左检查字符串是
double
传递给它,并返回一个int
,用于printf(“%.*lf”…
语句中,这样返回的int
既不会降低精度,也不会产生尾随零
我有一个工作的功能,但它是相当大的,因为它是为可读性和所有评论写的
为了总结这个函数,我计算了在10>d>=0
范围内获得double
所需的10个除法,只取小数部分并将其放入一个字符串中,其中有n个小数位,其中n=15-小数位数\u(我读到类型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);
}
int获取小数点后的数字(双d)
{
int i=0;/*有时您需要一个int*/
int pl=0;/*精度左=15-符号*/
int sigfights=1;/*d中的位数*/
字符行[20];/*用于查找小数点右侧的最后一个非零位*/
双温;/*用于破坏性计算的d的副本*/
/*查找小数点右边的数字*/
温度=d;
而(小于15)
{
如果(温度<0)
温度*=-1;
如果(温度<10)
打破
温度/=10;
++无花果;
}
/*此时10>温度>=0
*减量温度单位1>温度>=0*/
而(温度>1)
{
--温度;
}
如果(温度==0)
返回(0);
pl=15-sigfigs;/*如果小数点左边有n位,则右边有15-n位*/
开关(pl)
{
案例14:
sprintf(行,.14lf“,d);
打破
案例13:
sprintf(行,.13lf“,d);
打破
案例12:
sprintf(行“.12lf”,d);
打破
案例11:
sprintf(行“%.11lf”,d);
打破
案例10:
sprintf(行“%.10lf”,d);
打破
案例9:
sprintf(行“%.9f”,d);
打破
案例8:
sprintf(行“%.8lf”,d);
打破
案例7:
sprintf(行“%.7lf”,d);
打破
案例6:
sprintf(行“%.6lf”,d);
打破
案例5:
sprintf(行“%.5lf”,d);
打破
案例4:
sprintf(行“%.4lf”,d);
打破
案例3:
sprintf(行“%.3lf”,d);
打破
案例2:
sprintf(行“%.2lf”,d);
打破
案例1:
sprintf(行“%.1lf”,d);
打破
案例0:
返回(0);
打破
}
i=(strlen(line)-1);/*最后一个有意义的数字字符*/
当(1)/*从字符串末尾开始时,向左移动以检查第一个非零*/
{
如果(第[i]行=='0')/*如果末尾为0*/
{
--一,;
--pl;
}
其他的
{
打破
}
}
收益率(pl);
}
我注意到的第一件事是,你将temp
除以10
,这会导致精度下降
不是要让您停止或阻止您再次尝试,但正确实现这一点比您所展示的要复杂得多
盖伊·斯蒂尔(Guy L.Steele)和乔恩·怀特(Jon L.White)写了一篇名为“”的论文,详细介绍了一些陷阱,并介绍了一种用于打印浮点数的工作算法。这本书读得很好。可能没有更简单的方法。这是一个相当复杂的问题
您的代码无法正确解决此问题,原因如下:
- 大多数浮点运算的实际实现不是十进制的,而是二进制的。因此,当你将一个浮点数乘以10或除以10时,你可能会失去精度(这取决于数字)
- 尽管标准的
64位IEEE-754
浮点格式为尾数保留53
位,这相当于floor(log10(2^53))
=15
十进制数字,此格式的有效数字在精确打印时可能需要在小数部分最多包含一些1080
十进制数字,这似乎是您要问的问题
解决此问题的一种方法是在snprintf()
中使用%a
格式类型说明符,该说明符将使用十六进制数字作为尾数打印浮点值,而1999年的C标准保证,如果浮点格式为基数-2,则该说明符将打印所有有效数字(又名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
-th位置的二进制数字也在十进制表示法中点右侧的第i
-th位置产生最后一个非零十进制数字
因此,如果您知道二进制浮点数中最低有效非零位的位置,您可以计算出需要多少位十进制数字才能精确打印数字的小数部分
这就是我的程序所做的
代码:
您可以看到,π
和0.1
仅在15
个十进制数字之前为真,其余数字显示了真正四舍五入的数字,因为这些数字不能以二进制浮点格式精确表示
您还可以看到DBL_MIN
,最小的正标准化双
值,具有1022<
// 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,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;
}
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