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

Math 以安全的方式截断64位IEEE双倍到61位,math,floating-point,truncated,Math,Floating Point,Truncated,我正在开发一种编程语言,它使用标记的变量类型作为其主要值类型。3位用于类型(整数、字符串、对象、异常等),61位用于实际值(实际整数、指向对象的指针等) 很快,将是向语言添加float类型的时候了。我几乎有64位double的空间,所以我想在内部使用double进行计算。因为我的存储空间实际上短了3位,所以每次计算之后,我都必须对double进行四舍五入-基本上是61位double,尾数或指数短了3位 但是!我知道浮点运算充满了危险,做一些对实数来说合理的事情可能会给FP数学带来灾难性的结果,所

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

很快,将是向语言添加
float
类型的时候了。我几乎有64位double的空间,所以我想在内部使用double进行计算。因为我的存储空间实际上短了3位,所以每次计算之后,我都必须对double进行四舍五入-基本上是61位double,尾数或指数短了3位

但是!我知道浮点运算充满了危险,做一些对实数来说合理的事情可能会给FP数学带来灾难性的结果,所以我向专家们提出了一个开放式的问题:

这种方法可行吗?在长时间运行的计算中,通过每一步四舍五入,我会遇到严重的错误累积问题吗?为了避免这种情况,我有没有具体的方法来进行取整?是否有任何特殊值我无法以这种方式处理(我想到的是低于正常值)


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

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

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

三是从一个只有11的指数中借用的大量比特,但是您也可以考虑使用语言中的单精度32位格式(从主机调用单精度操作)。 最后,我给出了Jakub发现的另一个解决方案:从有效位借用三位,并在转换为49显式有效位、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清除标记

这是我自己的研究总结和@Pascal Cuoq的优秀文章中的信息

我们可以在两个位置截断所需的3位:指数和尾数(有效位)。这两种方法都会遇到一些问题,必须明确处理这些问题,才能使计算像使用假设的本机61位IEEE格式一样运行

截断尾数 我们将尾数缩短3位,形成
1s+11e+49m
格式。当我们这样做时,以双精度执行计算,然后在每次计算后舍入,这会使我们面临问题。幸运的是,对于中间计算,可以通过(从四舍五入到奇数)避免双舍入。有一种方法描述了这种方法,并证明了它对所有双精度的正确性——只要我们至少截断2位

C99中的可移植实现非常简单。由于舍入到奇数不是可用的舍入模式之一,因此我们通过使用进行模拟,然后在发生异常时设置最后一位。用这种方法计算出最后的
double
后,我们只需四舍五入到最近的位置进行存储

与完整的64位双精度(从15-17位到14-16位)相比,结果浮点的格式丢失约1位有效(十进制)数字

截断指数 我们从指数中取3位,得到
1s+8e+52m
格式。该方法(应用于OCaml中63位浮点的假设引入)在一篇文章中进行了描述。因为我们缩小了范围,所以我们必须处理正边和负边的超出范围的指数(通过简单地“四舍五入”到无穷大)。在负端正确执行此操作需要将输入偏压到任何操作,以确保在61位结果需要低于正常值时,我们在64位计算中获得低于正常值的值。对于每一个操作,这一点必须有所不同,因为重要的不是操作数是否低于正常值,而是我们是否期望结果为(61位)

由于我们借用了指数11位中的3位,结果格式的范围大大缩小。范围从10-308…10308下降到大约10-38到1038。似乎