这是我自己的研究和信息的总结,来自@Pascal Cuoq 的出色回答。
有两个地方可以截断我们需要的 3 位:指数和尾数(有效位)。这两种方法都遇到了必须明确处理的问题,以便计算表现得就像我们使用假设的本机 61 位 IEEE 格式一样。
截断尾数
我们将尾数缩短 3 位,形成1s+11e+49m
格式。当我们这样做时,以双精度执行计算,然后在每次计算后进行舍入会使我们面临双舍入问题。幸运的是,可以通过对中间计算使用特殊的舍入模式(round-to-odd)来避免双重舍入。有一篇学术论文描述了这种方法并证明了它对所有双精度数的正确性——只要我们截断至少 2 位。
C99 中的可移植实现很简单。由于 round-to-odd 不是可用的舍入模式之一,我们通过使用 来模拟它fesetround(FE_TOWARD_ZERO)
,然后在FE_INEXACT
发生异常时设置最后一位。以这种方式计算出最终结果后double
,我们只需四舍五入到最接近的位置进行存储。
与完整的 64 位双精度(从 15-17 位到 14-16 位)相比,生成的浮点数的格式丢失了大约 1 个有效(十进制)数字。
截断指数
我们从指数中取 3 位,得到一个1s+8e+52m
格式。这种方法(应用于在 OCaml 中假设引入 63 位浮点数)在一篇文章中进行了描述。由于我们缩小了范围,我们必须在正面(通过简单地将它们“四舍五入”到无穷大)和负面上处理超出范围的指数。在消极方面正确执行此操作需要将输入偏置到任何操作,以确保每当 61 位结果需要低于正常值时,我们在 64 位计算中得到低于正常值。对于每个操作,这必须稍有不同,因为重要的不是操作数是否是次正规的,而是我们是否期望结果是(在 61 位中)。
由于我们从指数的 11 位中借用了多达 3 位,因此生成的格式显着减小了范围。范围从 10 -308 ...10 308下降到大约 10 -38到 10 38。计算似乎没问题,但我们仍然损失了很多。
比较
这两种方法都会产生一个表现良好的 61 位浮点数。我个人倾向于截断尾数,原因有以下三个:
- round-to-odd 的“修复”操作更简单,操作之间没有区别,并且可以在计算之后完成
- 有证据证明这种方法的数学正确性
- 放弃一个有效数字似乎没有放弃双倍范围的很大一部分影响
尽管如此,对于某些用途,截断指数可能更有吸引力(特别是如果我们更关心精度而不是范围)。