4

我正在开发一种编程语言September,它使用标记的变体类型作为其主要值类型。3 位用于类型(整数、字符串、对象、异常等),61 位用于实际值(实际整数、指向对象的指针等)。

很快,就该float为语言添加类型了。我几乎有 64 位双精度的空间,所以我想在内部使用双精度进行计算。由于我实际上是 3 位存储空间不足,因此我必须在每次计算后将双精度数舍入 - 本质上会导致 61 位双精度数,尾数或指数短 3 位。

但!我知道浮点充满了危险,并且做一些在纸上听起来很明智的事情会对 FP 数学产生灾难性的结果,所以我有一个开放式问题要问专家:

这种方法是否可行?在每一步进行舍入时,我会在长时间运行的计算中遇到严重的错误累积问题吗?是否有一些特定的方法可以进行舍入以避免这种情况?是否有任何我无法以这种方式处理的特殊值(想到次正常值)?

理想情况下,我希望我的浮点数与原生 61 位双精度浮点数一样表现良好。

4

2 回答 2

10

我建议从双精度格式的指数字段中借用位。这是本文中描述的方法(您可以修改为从指数中借用 3 位而不是 1)。使用这种方法,所有不使用非常大或非常小的中间结果的计算的行为与原始双精度计算完全相同。即使是运行到新格式的次正规区域的计算,其行为也与 IEEE 标准化 1+8+52 61 位格式时的行为完全相同

相比之下,天真地从有效位借用任意数量的位会引入许多双舍入问题,更常见的是从 52 位有效位舍入到仅删除几位的有效位。正如您在对问题的编辑中建议的那样,从有效数字中借用一位将是最糟糕的,统计上一半的操作会产生双舍入结果,这与理想的“本机 61 位双精度”会产生的结果不同。这意味着基本运算不会精确到 0.5ULP,而是精确到3 / 4 ULP,精度的巨大损失将使许多现有的、精心设计的期望 0.5ULP 的数值算法脱轨。

不过,3 是从只有 11 的指数中借用的大量位,您也可以考虑在您的语言中使用单精度 32 位格式(从主机调用单精度操作)。

最后,我在这里展示了 Jakub 找到的另一个解决方案:从有效位借用三个位,并在转换为 49-explicit-significand-bit 中最接近的数字之前模拟中间双精度计算的四舍五入, 11 指数位格式。如果选择了这种方式,请注意,可以通过以下操作实现将自身舍入到 49 位有效位:

if ((repr & 7) == 4) 
  repr += (repr & 8) >> 1);   /* midpoint case */
else
  repr += 4;
repr &= ~(uint64_t)7; /* round to the nearest */

尽管处理与所考虑的具有相同表示的整数,但double即使数字从正常到次正常、从次正常到正常或从正常到无限,上面的代码片段仍然有效。您当然希望在上面已释放的三个位中设置一个标记。要从其未装箱表示中恢复标准双精度数,只需使用 清除标记repr &= ~(uint64_t)7;

于 2014-04-16T14:43:02.597 回答
0

这是我自己的研究和信息的总结,来自@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 的“修复”操作更简单,操作之间没有区别,并且可以在计算之后完成
  • 有证据证明这种方法的数学正确性
  • 放弃一个有效数字似乎没有放弃双倍范围的很大一部分影响

尽管如此,对于某些用途,截断指数可能更有吸引力(特别是如果我们更关心精度而不是范围)。

于 2014-04-17T11:30:14.557 回答