我得到精度损失时,转换一个大双(17位以上)数字为整数。
#include <stdio.h>
int main() {
int n = 20;
double acum = 1;
while (n--) acum *= 9;
printf("%.0f\n", acum);
printf("%llu\n", (unsigned long long)acum);
return 0;
}
这段代码的输出是:
12157665459056929000
12157665459056928768
我不能使用unsigned long long来进行计算,因为这只是一个伪代码,我需要真实的代码的精度,其中包括除法。
如果我增加小数的第一个输出变成,例如12157665459056929000.0000000000。我尝试了round(acum)和trunc(acum),两种情况下的结果都与第二个输出相同。它们不应该和第一个相等吗??
我知道float只有6位小数精度,double大约有17位。但这些数字有什么问题?!?
2条答案
按热度按时间kt06eoxx1#
这里有几个不同的问题。第一个是你试图计算920,这是一个相当大的数字。这个数字至少需要64位才能完全表示,它的十进制精确值是12157665459056928801。奇怪的是,这与您的程序打印的两个数字中的任何一个都不匹配。我们马上就会知道原因。
所有C的内置数据类型都有有限的精度,这意味着它们不能完全准确地表示每个数字。类型
double
通常具有53位精度,计算出 * 大约 * 17个十进制数字。所以我们马上可以看到,我们可能会遇到920的麻烦,正如我们所看到的,它需要64位或20位十进制数字。(An顺便说一句:人们很容易产生一种错误的印象,即“精度数字”意味着“小数点右边的数字”。但事实并非如此。“精度位数”是指任何地方的有效位数。因此,对于我们的数字12157665459056928801,我们不应该期望能够准确地表示它的所有20位数字。最后一个数字...8801将是不稳定的。)
实际上,类型
double
不能精确地表示64位数字12157665459056928801。它能得到的最接近的是12157665459056928768,它 * 看起来 * 像一个64位的数字,但实际上,它只有53 * 有效 * 位,因为最后11位都是0。这也意味着这个数字是2048(211)的整数倍。(You看不出12157665459056928768的低位都是0,除非你把它转换成十六进制或二进制:
0xa8b8b452291fe800
或0b1010100010111000101101000101001000101001000111111110100000000000
。但您会注意到前17位数字确实匹配。)现在,12157665459056928768实际上是程序打印的两个数字中的第二个。您的程序计算出
double
,近似值为12157665459056928768到920,然后它将这个数字完全准确地转换为unsigned long long
类型,并使用%llu
完全准确地打印出来。有道理那么另外一个号码12157665459056929000是从哪里来的呢?我怀疑你用的是微软的编译器。微软的
printf
版本在打印double
类型的值时采用了一个有趣的快捷方式:它计算并打印前17位数字,然后盲目地用0替换其余的数字。(看起来它至少正确地舍入了第17位数字。)这意味着它没有计算大多数double
值的精确的、全精度的十进制表示,尽管它最终匹配(或加强了)“类型double具有17位精度”这一简单但错误的假设。事实证明,要完全准确地计算和打印
double
值的所有十进制数字并不容易-这实际上是一个直到1990年才解决的问题-但printf
的其他版本(如MacOS和Linux下可用的)使用更多-复杂的技术,因此会将acum
的double
值打印为12157665459056928768。jgwigjjp2#
实际上,当我将acum的类型更改为 unsigned long long 时,如下所示:
unsigned long long acum = 1;
结果是:
12157665459056928801
当我使用Python计算准确答案时:
>>9**20 12157665459056928801L
看到了吗
12157665459056929000
根本不是一个准确的答案,实际上是准确答案的近似值。然后我这样修改代码:
printf("%llu\n", (unsigned long long)1.2157665459056929e+019);
printf("%llu\n", (unsigned long long)1.2157665459056928e+019);
printf("%llu\n", (unsigned long long)1.2157665459056927e+019);
printf("%llu\n", (unsigned long long)1.2157665459056926e+019);
结果是:
12157665459056928768
12157665459056928768
12157665459056926720
12157665459056926720
事实上,19位数超过了cpp的数字位数限制,转换这么大的数字的结果是不可预料和不安全的。