C语言 从IEEE 754双精度中窃取比特



如果有效数的最低有效位(s)被设置为随机值,那么对浮点数学的影响可能是什么?

解释:

语言PicoLisp使用一个结构(cell)分配所有值,该结构由两个"机器词"组成。在32位系统上,这意味着单元格是由两个32位指针或整数组成的8字节结构。单元格按其大小对齐,这意味着至少单词的最低三位可以自由用作类型和GC标记数据。

PicoLisp非常简约。该语言缺乏的(许多)东西之一是对浮点数的任何支持,而完全依赖于文档所称的"缩放定点"表示。我想尝试添加浮点支持会很有趣。

在32位系统上,64位浮点数可以很好地放在一个单元中,这意味着分配系统可以基本相同,除了一个小问题:所有64位都将被双精度数使用。但是GC期望使用位0作为GC标记位。简单地进行,在每个收集周期后,无论实际存储在双精度型中的值是什么,第0位将被设置为零。

(这是假设大小和端序都正确排列。为此,假设他们这样做;如果他们不这样做,那么整个问题就完全无关紧要了,必须使用不同的策略。

那么:对于使用硬件浮点运算的通用数学来说,这是一个多大的问题?

如果它所做的只是稍微降低double的精度,那么我认为这实际上不是一个问题:只要记录了解释器中的浮点数学不像用户期望的那样精确,如果他们需要严格精确的行为,他们应该回到定点或库或其他东西。我对它的直观理解是应该是这样的,因为它是最低有效位(甚至在转换为字符串时都不会显示…?)。

另一方面,浮点是巫术。这种篡改比特的行为是否会严重影响数学的有用性,或者影响产生任何一致结果的能力?

(我已经考虑了分配器的其他几种实现可能性。我特别感兴趣的是这个策略是否非常愚蠢,因为它是最简单的,我很懒。)

只要外部代码总是看到的值就好像低位已经四舍五入,你通过将尾号四舍五入到最接近的偶数值来实现这一点,对于正常的计算来说,这是可以的。

也就是说,对于以:

结尾的尾数

00: do nothing

10:不做任何事

01:尾数

减去1

11:尾数加1(如果溢出,则需要增加指数并清除尾数)

如果你不符合你的四舍五入,只是去掉低的位,你会给你的计算带来一个非常轻微的向下的偏差。向偶数四舍五入是IEEE抵消这种向下偏差的方法。

小心+/-无穷大,因为设置低位将把它们变成nan,这是非常脆弱的工作(突然你所有的比较操作开始失败)。

StilesCrisis提出的方案导致双舍入,这通常被认为是一件坏事。

我想建议的另一个选择:

显示并计算每个PicoLisp浮点数,就好像它比它大2512倍。这意味着双重加法和减法几乎保持不变,乘法和除法只需要一次便宜的调整,而其他操作(库调用)需要两次调整,一次在之前,一次在之后。

每次操作后,检查溢出(现在发生的频率更高,每次偏差结果大于1.0)。

如果你这样做,不是借有效数的最低有效位,而是借指数的最高有效位。这需要一些位变换来加载和存储浮点数,但这将更容易向使用该系统的程序员解释,并且为IEEE 754类属性设计的算法将继续工作(除非它们现在溢出)。


代码可能看起来像这个稍微测试过的实现。在另一个上下文中的类似实现是这篇博文的对象,它提供了更多的解释。

void smalldouble_to_cell(void*p, double d)
{
  union u u;
  u.d = d;
  unsigned long long rest = u.u & 0x7fffffffffffffff;
  unsigned long long packed;
  if (rest > 0x7ff0000000000000)
    /* NaN */
    packed = u.u & 0xfffffffffffffffe;
  else 
    {
      unsigned long long sign = u.u & 0x8000000000000000;
      if (rest >= 0x3ff0000000000000)
    rest = 0x3ff0000000000000;
      packed = sign | (rest << 1);
    }
  memcpy(p, &packed, 8);
}
void double_to_cell(void *p, double d)
{
  smalldouble_to_cell(p, ldexp(d, -512));
}

改变有效数的最低有效位通常会产生你想要的效果——它会改变数字中记录的最低有效位

但是在一些特殊情况下你会遇到问题。

你会看到一些与正无穷或负无穷编码相反的问题。无穷大是用最大可能的指数和有效的零来编码的。如果修改有效数,则infinite将变成NaN。

最新更新