截断64位IEEE以安全的方式加倍到61位

时间:2014-04-16 14:38:03

标签: math floating-point truncated

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

很快,就可以在语言中添加float类型了。我几乎有64位双精度空间,所以我想在内部使用双精度计算。由于我实际上是3比特 short 用于存储,我将不得不在每次计算后关闭双打 - 实质上导致61位双尾数,尾数或指数缩短3位。

但是!我知道浮点数充满了危险,做一些听起来对于真实数字纸张合理的事情会导致FP数学产生灾难性后果,所以我对那里的专家提出了一个开放式的问题:

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

理想情况下,我希望我的浮点数与本机61位双精度数一样好。

2 个答案:

答案 0 :(得分:10)

我建议借用双精度格式的指数字段中的位。这是this article中描述的方法(您将修改为从指数借用3位而不是1)。使用这种方法,所有不使用非常大或非常小的中间结果的计算都与原始的双精度计算完全相同。即使是新格式的次正常区域的计算也会像IEEE标准的1 + 8 + 52 61位格式那样完全表现出。

相比之下,天真地从有效数据中借用任意数量的位会引入许多double-rounding个问题,所以更频繁的是从52位有效数字舍入到只有少量位的有效数字。正如您在编辑问题时所建议的那样,从有效数字中借用一点将是最糟糕的,其中一半的操作在统计上产生双舍入结果,这与理想的“本机61位双”产生的结果不同。这意味着,不是精确到0.5ULP,基本操作将精确到 3 / 4 ULP,这会严重损失准确性,这会破坏许多现有的,精心设计的数值算法,期望0.5ULP。

三是从一个只有11的指数借用的大量位,但你也可以考虑在你的语言中使用单精度32位格式(从主机调用单精度操作)

最后,我在这里给出了Jakub发现的另一个解决方案:从有效数据中借用三个比特,并在转换为49-explicit-significand中最接近的数字之前模拟round-to-odd进行中间双精度计算位,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;清除标记。

答案 1 :(得分:0)

这是我自己的研究和来自@Pascal Cuoq的优秀answer的信息摘要。

我们可以在两个地方截断我们需要的3位:指数尾数(有效数字)。这两种方法都遇到了必须明确处理的问题,以便计算的行为就像我们使用假设的原生61位IEEE格式一样。

截断尾数

我们将尾数缩短3位,产生1s+11e+49m格式。当我们这样做时,以双精度执行计算然后在每次计算后舍入会使我们面临double rounding个问题。幸运的是,对于中间计算,using a special rounding mode(round-to-odd)可以避免双舍入。有一个academic paper描述了这种方法并证明了它对所有双精度的正确性 - 只要我们截断至少2位。

C99中的便携式实现非常简单。由于round-to-odd不是可用的舍入模式之一,我们使用fesetround(FE_TOWARD_ZERO)模拟它,然后在FE_INEXACT例外发生时设置最后一位。以这种方式计算最终double之后,我们只需将其舍入到最近的存储位置。

与完整的64位双精度(从15-17位到14-16位)相比,生成的浮点数的格式丢失了大约1个有效位(十进制)。

截断指数

我们从指数中取3位,得到1s+8e+52m格式。这种方法(适用于在OCaml中假设引入63位浮点数)在article中描述。由于我们减小了范围,我们必须在正面(通过简单地将它们“舍入”到无穷大)和负面处理超出范围的指数。在负面正确执行此操作需要将输入偏置到任何操作,以确保每当61位结果需要低于正常时我们在64位计算中得到次正规。对于每个操作,这必须有所不同,因为重要的不是操作数是否是低于正常,而是我们是否期望结果是(在61位)。

由于我们借用了11位指数中的3位,所以格式显着缩小了范围。该范围从10 -308 ... 10 308 下降至约10 -38 至10 38 。似乎可以计算,但我们仍然失去了很多。

比较

这两种方法都能产生良好的61位浮点数。我个人倾向于截断尾数,原因有三个:

  • 圆形到奇数的“修复”操作比较简单,操作与操作没有区别,可以在计算后完成
  • 证明了这种方法的数学正确性
  • 放弃一个重要数字似乎不如放弃双重范围的大部分

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