【问题标题】:Square root union and Bit Shift平方根联合和位移
【发布时间】:2015-02-01 15:20:29
【问题描述】:

我发现这段代码可以得到平方根,令我惊讶的是它使用联合和位移的方式,这就是代码:

float sqrt3(const float x)  
{
  union
  {
    int i;
    float x;
  } u;

  u.x = x;
  u.i = (1<<29) + (u.i >> 1) - (1<<22); 
  return u.x;
} 

首先将 x 的值保存在 u.x 中,然后将值赋给 u.i 然后是数字的平方根并神奇地出现u.x

¿有人向我解释一下这个算法是如何实现的?

【问题讨论】:

  • 它不会返回数字的平方根。
  • 好吧,首先这段代码表现出 UB(未定义行为),因此不应该信任它可以在任何平台上工作。但其次,您可能值得阅读 IEEE 754 binary32 浮点格式;特别是,它将 32 位 float 分为 1 个符号位、8 个指数位和 23 个尾数位,因此 sign* 1.mantissa * pow(2, exponent-127) 您的代码所做的就是将指数部分减半并损坏尾数。这样做是因为数字的平方根有一个大约一半大小的指数(例如 4000000 ~ 10^6、4000 ~ 10^3,指数减半)。
  • 返回平方根我想知道它是如何工作的
  • @Iwillnotexist Idonotexist : 因为这有未定义的行为

标签: c bit unions shift


【解决方案1】:

上述代码表现出 UB(未定义行为),因此不应信任它可以在任何平台上工作。这是因为它写入 union 的成员并从与上次写入 union 时使用的成员不同的成员读回。它还很大程度上取决于字节序(多字节整数中字节的顺序)。

但是,它通常会做预期的事情,并了解为什么值得您阅读IEEE 754 binary32 floating-point format

IEEE754 binary32 格式速成课程

IEEE754 通常将一个 32 位浮点数分为 1 个符号位、8 个指数位和 23 个尾数位,从而给出

                   31 30-23           22-0
Bit#:               ||------||---------------------|
Bit Representation: seeeeeeeemmmmmmmmmmmmmmmmmmmmmmm
Value:              sign * 1.mantissa * pow(2, exponent-127)

这个数字本质上是“科学记数法,以 2 为底”。

作为细节,指数以“有偏”的形式存储(即,它的值 127 个单位太高了)。这就是为什么我们从编码的指数中减去 127 得到“真正的”指数。

简短说明

您的代码所做的是 指数 部分减半并损坏尾数。这样做是因为一个数字的平方根有一个大约一半大小的指数

以 10 为基数的示例

假设我们想要 4000000 = 4*10^6 的平方根。

4000000 ~ 4*10^6  <- Exponent is 6
4000    ~ 4*10^3  <- Divide exponent in half

只需将指数 6 除以 2,得到 3,并使其成为新指数,我们就已经在正确的数量级内,并且更接近事实,

2000 = sqrt(4000000)

.

【讨论】:

  • 2019 年更新:C99 明确允许这样做,因此它只是实现定义的。但是,它仍然是 C++ 中的 UB。
【解决方案2】:

你可以在维基百科上找到完美的解释:

Methods of computing square roots

参见章节:依赖于浮点表示的近似值

所以对于 IEEE 格式的 32 位单精度浮点数 (值得注意的是,功率的偏差为 127 形式)您可以通过解释其二进制来获得近似对数 表示为 32 位整数,按 2^{-23} 缩放,以及 去除 127 的偏差,即

要获得平方根,请将对数除以 2 并将值转换回来。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-08
    • 2021-04-10
    • 2021-11-28
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多