C语言 17位或更多位数的双精度

wj8zmpe1  于 2023-05-28  发布在  其他
关注(0)|答案(2)|浏览(237)

我得到精度损失时,转换一个大双(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位。但这些数字有什么问题?!?

kt06eoxx

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,除非你把它转换成十六进制或二进制:0xa8b8b452291fe8000b1010100010111000101101000101001000101001000111111110100000000000。但您会注意到前17位数字确实匹配。)
现在,12157665459056928768实际上是程序打印的两个数字中的第二个。您的程序计算出double,近似值为12157665459056928768到920,然后它将这个数字完全准确地转换为unsigned long long类型,并使用%llu完全准确地打印出来。有道理
那么另外一个号码12157665459056929000是从哪里来的呢?我怀疑你用的是微软的编译器。微软的printf版本在打印double类型的值时采用了一个有趣的快捷方式:它计算并打印前17位数字,然后盲目地用0替换其余的数字。(看起来它至少正确地舍入了第17位数字。)这意味着它没有计算大多数double值的精确的、全精度的十进制表示,尽管它最终匹配(或加强了)“类型double具有17位精度”这一简单但错误的假设。
事实证明,要完全准确地计算和打印double值的所有十进制数字并不容易-这实际上是一个直到1990年才解决的问题-但printf的其他版本(如MacOS和Linux下可用的)使用更多-复杂的技术,因此会将acumdouble值打印为12157665459056928768。

jgwigjjp

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的数字位数限制,转换这么大的数字的结果是不可预料和不安全的。

相关问题