为什么这不会导致溢出?

Why doesn't this cause overflow?

#include <stdio.h>

int main() {
    double b = 3.14;
    double c = -1e20;
    
    c = -1e20 + b;
    
    return 0;
}

据我所知,“double”类型有 52 位小数。为了使 3.14 的指数与 -1e20 一致,3.14 的派系部分超过了 60 位,这永远不适合 52 位。 在我的理解中,除 52 之外的其余小数位(大致计 14 位)会像这样侵入未分配的内存 space。 rough drawing

所以我在调试模式 (gdb) 下检查了内存映射,怀疑变量 b 或 c 旁边的位会被破坏。但是我看不到任何变化。我在这里错过了什么?

你混淆了两个截然不同的东西:

  • 缓冲区overflow/overrun

您添加的图像显示了缓冲区溢出时发生的情况。 就像定义 char[100] 和写入索引 150 一样。然后内存布局很重要,因为您可能会破坏相邻变量。

  • 数据类型的值溢出

您的代码显示的只能是值溢出。 如果你这样做 int a= INT_MAX; a++ 你会得到一个整数溢出。 这只会影响结果值。 它不会导致变量的大小增加。 int 将始终保持 int。 您不会侵入数据类型之外的任何内存区域。 根据数据类型和架构,溢出的位可以被截断或者可以应用一些饱和度来将值设置为 maximum/minimum 可表示的值。

我没有检查你的值,但没有在调试器中检查或打印 c 的值,你无法告诉那里溢出的任何信息。

Floating-point 算术未定义为通过写出操作数的所有位、使用所有涉及的位执行算术并将这些位存储在内存中来工作。相反,基本 floating-point 操作的工作方式是执行每个操作“就好像它首先产生了一个正确到无限精度和无限范围的中间结果”,然后四舍五入到一个可以在 [=75] 中表示的结果=] 格式。 “好像”很重要。这意味着当计算机处理器设计者设计 floating-point 算术指令时,他们会弄清楚如何计算最终的舍入结果。处理器并不总是需要“写出”所有位来执行此操作。

考虑一个使用具有四位有效数字的十进制 floating-point 的示例。如果我们加上6.543•1020和1.037•17(等于0.001037•1020), infinite-precision 结果将是 6.544037•1020,然后将其四舍五入到最接近 four-significant-digit 格式的数字将得到 6.544•1020。但是我们不必写出 infinite-precision 结果来计算它。我们可以计算结果是 6.544•1020 加上一个很小的分数,然后我们可以丢弃这个分数而不用实际写出它的数字。这就是处理器设计人员所做的。加法、乘法和其他指令计算结果的主要部分,并仔细管理有关其他部分的信息,以确定它们是否会导致结果在最后一位向上或向下舍入。

由此产生的行为是,给定任何两个采用用于 double 的格式的操作数,计算机总是以相同的格式产生结果。它不会产生任何额外的位。

补充

double常用格式的小数部分有53位。 (这是 IEEE-754 binary64 格式,也称为 双精度 。)小数部分称为 significand。 (您可能会看到它被称为 尾数 ,但这是对数的小数部分的旧术语。首选术语是“有效数”。有效数是线性的;尾数是对数的.) 你可能会看到有人描述有效数字有 52 位,但那指的是 floating-point 值编码的一部分,而它只是其中的一部分。

在数学上,floating-point表示被定义为sfbe,其中b为固定数基,s提供一个符号(+1或-1),f是一个固定位数p的数字be 是固定范围内的指数。 p称为格式精度,对于binary64格式为53。当这个数字被编码成位时,f 的最后 52 位存储在有效位字段中,这就是 52 的来源。但是,第一位也通过指数字段进行编码。每当存储的指数字段不为零(或所有位的特殊值)时,表示 f 的第一位为 1。当存储的指数字段为零时,表示f 的第一位是 0。所以编码中有 53 位。