【问题标题】:bitwise stochastic rounding按位随机舍入
【发布时间】:2021-05-25 13:32:15
【问题描述】:

我有这段 C 代码,它将 binary64 值随机舍入为 binary32。问题是我不太完全理解代码。我知道它直接对浮点数的位进行操作,但我无法理解发生了什么。能否请您与我分享一些见解?

float function(double x){
  uint64_t temp = *(uint64_t*)&x;
  uint32_t r = (rand() * (0xFFFFFFFF/RAND_MAX)) % 0x1FFFFFFF;
  temp += r;
  temp = temp & 0xFFFFFFFFE0000000;

  return (float)*(double *)&temp;
}

位掩码代表什么? (我的直觉告诉我这与指数和尾数如何以二进制格式表示有关,但我无法将其可视化)
为什么随机变量 r 是这样计算的?
通过代码进行的交互会是什么样子?

【问题讨论】:

  • 似乎是特定于平台的,但请扩展为可编译的示例,以便我们查看机器代码。
  • 0xFFFFFFFFE0000000 涵盖binary64 的 1 个符号位 + 11 个指数位 + 23 个尾数位,其中最后一个是 binary32 中的尾数位数。所以除了上溢/下溢,temp 的最终值应该无损地转换成binary320x1FFFFFFF 覆盖了binary64 数字的29个低位尾数,它们用于四舍五入。
  • r 只是生成一个介于 0 和 0x1FFFFFFF 之间的随机数——括号会产生一些混乱,模数会将其限制在该范围内。 rand 的大多数实现都很垃圾,它们不会是很好的随机数。
  • 这种类型的双关语成语:uint64_t temp = *(uint64_t*)&x; 调用未定义的行为。我建议改用memcpy();现代编译器知道如何优化这一点。
  • @njuffa 你的解释很有见地,非常感谢。同样,感谢所有评论

标签: c floating-point bit-manipulation rounding stochastic


【解决方案1】:

uint64_t temp = <em>(uint64_t</em>)&amp;x;

这是获取代表double x 的位的错误尝试。这很糟糕,因为它违反了 C 的别名规则 (C 2018 6.5 7)。正确的代码是uint64_t temp; memcpy(&amp;temp, &amp;x, sizeof temp);uint64_t temp = (union { double d; uint64_t u; }) { x } .d;。前者将x 的字节复制到temp 中,后者使用复合文字来创建一个临时对象,它是一个联合,用于重新解释位。这两者都受 C 标准支持。

uint32_t r = (rand() * (0xFFFFFFFF/RAND_MAX)) % 0x1FFFFFFF;

* (0xFFFFFFFF/RAND_MAX)) 尝试将rand 的结果缩放到区间 [0, FFFFFFFF16]。它可能做得不完美。然后% 0x1FFFFFFF 将其缩小到区间 [0, 1FFFFFFF16)。注意结束 )] — 这是一个半开区间,不包括 1FFFFFFF16。这里有一些问题:

  • 这可能是一个错字; &amp; 0x1FFFFFFF 将干净地提取低 29 位,在完全闭合的区间 [0, 1FFFFFFF16] 中产生结果。使用 % 会产生不同的结果,而没有明显的数学目的,并且会强制进行耗时的除法。
  • 对于%&amp;,没有明显的理由首先缩放到FFFFFFFF16;一个人可能会直接进入所需的最终间隔。
  • 这只会产生积极的结果;这个数字只会在数量上增加或不变,永远不会减少。这可能是需要的,但目前还不清楚为什么。缺乏关于这一点和其他点的文档表明缺乏代码质量。

temp += r;

这会将随机数添加到double 的低位。有时,它会导致高位进位。 (如果高位全为1,也可以带入指数域。)

temp = temp &amp; 0xFFFFFFFFE0000000;

这会清除低 29 位。在 floatdouble 常用的 IEEE-754 binary32 和 binary64 格式中,float 有效位有 24 位(在主有效位字段中编码了 23),double 有效位有 53 位(52编码在主有效位字段中),因此差为 29。因此,如果指数在 float 范围内,则清除 double 编码中的低 29 位将产生一个可精确表示为 float 的数字.

清除这些位的目的可能是防止在转换为 float 期间进行第二次向上舍入,如下所示。上一行中的加法 temp += r; 可能会导致有效数字的高位进位,因此其意图可能是确保数字只增加一个单位,而不是两个单位。

return (float)*(double *)&amp;temp;

与上面的第一行一样,这是将位重新解释为double 的错误尝试。 (之后它被强制转换为 float,这对于标准 C 来说是不必要的,因为 return 语句的操作数会自动转换为函数的返回类型,但是,如果使用严格的代码检查,它可能会静音关于缩小转换的警告。)正确的代码是 memcpy(&amp;x, &amp;temp, sizeof x); return x;return (union { uint64_t u; double d }) { temp } .u;

【讨论】:

  • 很高兴阅读您的评论。我现在完全理解正在发生的事情,并且能够更好地摆弄代码。非常感谢您花时间给出如此全面的解释。很多赞!!!! ?
  • @chux-ReinstateMonica:谢谢,注意到了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
  • 2022-08-18
  • 1970-01-01
  • 1970-01-01
  • 2015-10-14
  • 2020-10-01
  • 1970-01-01
相关资源
最近更新 更多