在 C 中添加两个大双数会得到不正确的结果

Adding two large double numbers in C gives an incorrect result

提问人:TheDoctor Bombastic 提问时间:10/18/2020 最后编辑:ggorlenTheDoctor Bombastic 更新时间:10/18/2020 访问量:593

问:

我想问你一个问题,关于在 C 语言中将两个大数字相加。

假设有两个数字是双倍的:1.31E+42 和 1.399E+43。

如果我在Excel中进行添加,结果是153000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000这应该是正确的。

如果我用 C 语言进行添加,结果15299999999999999804719125983728080953278464。

差异是相当巨大的。有谁知道如何在 C 语言中将双倍的大数相加或相乘时获得正确的结果?

我必须添加另一个重要信息。一件事是把它打印出来。我知道可以按照你们的建议打印号码。但我也需要它作为另一部作品的价值。具体来说,这是一项分析两个圆的任务——它们的交点和/或接触(如果它们接触外部或内部,或者如果有交点)。https://www.bbc.co.uk/bitesize/guides/z9pssbk/revision/4如果两个圆的中心之间的距离 (V) 等于它们的半径之和(外部触摸)或它们半径之差(内部触摸),则两个圆将接触。因此,我必须对外部触摸进行加法,然后比较它们中心V之间的距离是否与它们的半径之和相同。因此,这不仅仅是打印出值。

第一圈:

Sx = -3.2E+41, Sy = -3.31E+42, R = 1.31E+42

第二圈:

Sx = 1.354E+43, Sy = 3.17E+42, R = 1.399E+43

在 C 语言中,它们中心之间的距离为:

V = 15300000000000002280599204554488630751526912.000000

它们的半径之和是 C 语言:

SumR =15299999999999999804719125983728080953278464.000000

根据参考,他们应该从外部触摸,因此,如果我执行以下条件,我会得到没有外部触摸的信息。

if (fabs(V - SumR) < 0.001)
  printf("There is an external touch")
else
  printf("No external touch")
c 点浮 点精度

评论

1赞 Eric Postpischil 10/18/2020
结果不是Excel中的“1.53E+43”。这正是它使用默认格式向您显示的内容。实际结果是不同的,可能与你用 C 得到的结果相同。
0赞 pmg 10/18/2020
尝试 printf(“delta %f\n”, 153000000 15299999999999999804719125983728080953278464 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
2赞 Weather Vane 10/18/2020
我不认为差异是“巨大的”。它在第 18 位数字处有所不同,这就是所有好处。如果输出更多的小数,会不会更大?在滑尺时代,我们得到的只是 3 位数字,在超长(圆柱形)滑尺上可能是 4 或 5 位。但作为工程师,为了安全起见,我们无论如何都会将其乘以 3。double
0赞 chux - Reinstate Monica 10/18/2020
@WeatherVane 为了好玩:纸质滑尺
1赞 Weather Vane 10/18/2020
@chux-恢复莫妮卡整洁!我还有一个 12 英寸的滑尺和一个圆柱形的滑尺,但我找不到日志表。如果我需要的话,我可以用 C 程序制作一个集合。

答:

2赞 Basile Starynkevitch 10/18/2020 #1

如果需要处理大数字,则应使用任意精度算术(也称为 bignums)库,例如 GMPlib

如果您想使用浮点数,请花时间阅读浮点指南IEEE 754 来了解它们。它们不遵循实数的直观属性(例如,大多数运算都不是关联的)。

当然,阅读 Modern C 和这个 C 参考网站,然后是 C11 草案标准 n1570

3赞 Eric Postpischil 10/18/2020 #2

浮点运算近似于实数算术。在将十进制数转换为二进制浮点数或进行浮点运算时,通常不应期望使用实数运算会得到的结果。

此答案假定您的 C 实现使用 IEEE-754 binary64 格式,并使用四舍五入到最接近的平数来执行算术运算,包括从十进制到 .doubledouble

binary64 格式有一个符号、一个 53 位有效数(数字的“分数部分”)和一个 11 位指数。

此格式不能表示 1.31•1042。它可以表示的最接近的值是 1310000000000000060347708657386176332693504。当 C 源文本包含 时,编译器会将其转换为 1310000000000000060347708657386176332693504。如果我们用十六进制来表示有效数,它是 1.E137CED6DF0D116•2139。您可以看到最初的“1”和另外 13 位十六进制数字(每个 4 位)组成 53 位。1.31e42

格式也不能表示 1.399•1043。它可以表示的最接近的值是 13989999999999999899113922237014438982975488。当 C 源文本包含 时,编译器会将其转换为 13989999999999999899113922237014438982975488。使用十六进制,这是 1.4131D470653E916•21431.399e43

当这些相加时,普通的数学结果是不可表示的。生成的结果是最接近的可表示数字,即 15299999999999999804719125983728080953278464。使用十六进制,这是 1.5F45515DD32F616•2143

这是浮点运算所预期的正确结果。获得符合您目的的“正确”结果取决于您想要完成的任务。出于许多目的,使用浮点获得近似结果就足够了,并且人们只需理解结果是近似的。如果需要确切的结果,则必须使用替代格式和软件。

评论

0赞 0___________ 10/18/2020
我要补充一点,如果您从 OP 问题中输入一个数字,excell 的精度非常有限,并且四舍五入算法很奇怪15299999999999900000000000000000000000000000.00