【问题标题】:How does this approximation of division using bit shift operations work?这种使用位移运算的除法近似是如何工作的?
【发布时间】:2015-10-11 09:55:15
【问题描述】:

java.util.DualPivotQuicksort中,出现以下代码行:

// Inexpensive approximation of length / 7
int seventh = (length >> 3) + (length >> 6) + 1; 

变量length 是一个大于或等于47 的int

我熟悉带符号右移运算符的工作原理。但我不知道为什么这些特定的操作会导致除以 7 的近似值。有人可以解释一下吗?

【问题讨论】:

  • 右移 3 相当于什么除法?

标签: java math bit-manipulation bit-shift


【解决方案1】:

>> 是位移位。你向右移动的每一位,实际上都除以 2。

因此,(length >> 3)length/8(向下舍入),(length >> 6)length/64

(length/8)+(length/64) 大约是length*(1/8+1/64) = length*0.140625(大约)

1/7 = 0.142857...

末尾的+1 可以拆分为每个术语的+0.5,这样length/8 会四舍五入到最近(而不是向下),length/64 也四舍五入到最近。


一般来说,您可以轻松地逼近1/y,其中y = 2^n+-1 使用类似的位移逼近。

无限几何级数是:

1 + x + x^2 + x^3 + ... = 1 / (1 - x)

乘以 x:

x + x^2 + x^3 + ... = x/(1 - x)

并替换为x = 1/2^n

1/2^n + 1/2^2n + 1/2^3n + ... = (1/2^n) / (1 - 1/2^n)
1/2^n + 1/2^2n + 1/2^3n + ... = (1/2^n) / ((2^n - 1)/2^n)

1/2^n + 1/2^2n + 1/2^3n + ... = 1 / (2^n - 1)

这近似于y = 2^n - 1

要近似 y = 2^n + 1,请替换为 x = -1/2^n

- 1/2^n + 1/2^2n - 1/2^3n + ... = (-1/2^n) / (1 + 1/2^n)
1/2^n - 1/2^2n + 1/2^3n - ... = (1/2^n) / ((2^n + 1)/2^n)

1/2^n - 1/2^2n + 1/2^3n - ... = 1 / (2^n + 1)

然后将无限级数截断到所需的精度。

【讨论】:

  • 我明白了。最后加 1 怎么样,是为了“平衡”四舍五入吗?
  • 是的,它增加了一半的长度/8,一半的长度/64,所以平均而言,它们是四舍五入而不是向下舍入。
【解决方案2】:

在众所周知的等式中设置x = 1/8

1 + x + x^2 + x^3 + ... = 1 / (1 - x)

并简化,给出

1/8 + 1/64 + 1/512 + ...  = 1/7

在您的示例中,将其两边乘以 length,得到 ​​p>

length / 7 = length / 8 + length / 64 + length / 512 + ...

请注意,这是“精确”除法,而不是整数除法 - 我正在编写数学,而不是 Java 代码。

然后近似值假设第三项和后续项太小而无所谓,平均而言,length / 8length / 64 中的一项可能需要向上取整,而不是向下取整。所以,现在使用整数除法,length / 7 = length / 8 + length / 64 + 1 是一个非常好的近似值。

如果length 是正数,您使用位运算符给出的表达式只是另一种写法。

【讨论】:

    【解决方案3】:

    为 ronalchn 的回答添加数学背景:

    由于7=8-1=8*(1-1/8),几何级数除以7等于乘以

    1/7 = 1/8·(1+1/8+1/8²+1/8³+…) = 1/8+1/8²+1/8³+…


    要对除以 5 做同样的事情,可以使用 3·5=16-1,因此

    1/5 = 3/16·(1+1/16+1/16²+…)

    这会邀请像

    这样的公式
    (3*n)<<4 + (3*n) << 8 + 1
    

    【讨论】:

    • 对于1/5,乘法不一定便宜,所以更好的近似值可能是1/4-1/16+1/64-1/256+...
    • 是的,这可能会更好,即使您需要更多条款。编译器可以通过重用最后移位的值来优化这些连续的位移。
    【解决方案4】:

    计算所有的值

    n/8 + n/64 - n/7
    

    误差线性增长,同时保持负数。

    下面的列表显示给定错误首次出现的时间

    n = 7 e = -1
    n = 63 e = -2
    n = 511 e = -3
    n = 959 e = -4
    n = 1407 e = -5
    n = 1855 e = -6
    n = 2303 e = -7
    n = 2751 e = -8
    n = 3199 e = -9
    n = 3647 e = -10
    n = 4095 e = -11
    n = 4543 e = -12
    n = 4991 e = -13
    n = 5439 e = -14
    n = 5887 e = -15
    n = 6335 e = -16
    n = 6783 e = -17
    n = 7231 e = -18
    n = 7679 e = -19
    n = 8127 e = -20
    n = 8575 e = -21
    n = 9023 e = -22
    n = 9471 e = -23
    n = 9919 e = -24
    ...
    

    比例明显趋于1/448 = 1/8 + 1/64 - 1/7

    【讨论】:

      猜你喜欢
      • 2017-06-06
      • 2023-03-13
      • 2023-04-01
      • 2015-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-25
      • 1970-01-01
      相关资源
      最近更新 更多