【问题标题】:Right Shift to Perform Divide by 2 On -1右移执行除以 2 On -1
【发布时间】:2011-01-09 18:33:12
【问题描述】:

我知道我可以使用右移执行除以 2。

为简单起见,采用 4 位数字系统

-1 - 1111
-2 - 1110
-3 - 1101
-4 - 1100
-5 - 1011
-6 - 1010
-7 - 1001
-8 - 1000
7  - 0111
6  - 0110
5  - 0101
4  - 0100
3  - 0011
2  - 0010
1  - 0001
0  - 0000

如果我尝试执行

6 / 2 = 0110 >> 1 = 0011 = 3
-6/ 2 = 1010 >> 1 = 1101 = -3

对 +ve 和 -ve 数字都有效

然而,当谈到 1

1 / 2 = 0001 >> 1 = 0000 = 0
-1/ 2 = 1111 >> 1 = 1111 = -1

似乎-1有一个特殊情况,右移然后将其移动到负无穷大。

目前,我需要对此进行特殊的 if 检查,因为我期望 -1 / 2 = 0。

我想知道你们如何处理代码中的这个异常?你放了一个if检查?

【问题讨论】:

    标签: java algorithm


    【解决方案1】:

    有一天我感到无聊,并为 2 个东西的力量分析了分工与轮班;我想我会把它贴在这里给任何有兴趣的人。

    在 Windows 上的 HotSpot VM 1.6 上,使用 j /= 4 在 -100000000 到 100000000 之间运行大约需要 12 秒,而使用 j = (j >= 0) ? j >> 2 : ~(~j+1 >> 2) + 1; 只需 2.5 秒。

    Linux 上的 OpenJDK VM 1.6 的分割时间为 5.5 秒,轮班时间为 1.5 秒。

    这表明 JIT 编译器实际上并没有为 2 除法的幂做任何花哨的事情。

    GCC 设法优化了划分,使其比翻转和移位更快。

    ~(~j+1 >> 2) + 1 使用二进制补码将数字翻转为正数,移位并翻转回来。

    long j = 0;
    for (long i = -100000000; i < 100000000; i++) {
        j = i;
        j /= 4;
    }
    System.out.println(j);`
    

    对比

    long j = 0;
    for (long i = -100000000; i < 100000000; i++) {
        j = i;
        j = (j >= 0) ? j >> 2 : ~(~j+1 >> 2) + 1;
    }
    System.out.println(j);`
    

    【讨论】:

    • 什么意思?基准代码、计算机规格或更详细的时间安排?
    【解决方案2】:

    @Anon 在技术上是正确的。

    但是,最佳实践是使用 / 运算符进行除法,并将微优化留给 JIT 编译器。 JIT 编译器能够将常量除法优化为移位/加法序列...当这是执行平台的最佳操作时

    做这种事情(可能)是一种过早的优化,如果您的代码需要在多个 Java 平台上快速运行,它可能是一种反优化。

    【讨论】:

    • 这确实是最好的答案。这种代码更改至少是该过程中的第三步:第一步是分析代码以确定这是一个重要的瓶颈,第二步是了解生成的字节码/JIT 是什么,并认识到这是次优的.
    【解决方案3】:

    我不想这么说,但我没有在我的代码中处理这个问题,因为我不使用位移来进行乘法或除法。我闻起来像premature optimisation

    为什么你认为你需要用位移而不是更易读的x / 2来做除法?

    【讨论】:

      【解决方案4】:

      任何负奇数都不起作用。但是,要回答您的问题,如果您知道可以有负数,只需除以 2。这将通过 jit/编译器的修复变成一个转变。

      【讨论】:

      • +1 注意,如果你想除以 2,你可以试试除法运算符,看看效果如何。
      • +1 - 也感谢您强调子优化通常不会超过编译器所做的工作,并且“天真”选项通常在这样的基本内容上是有效且正确的。
      • 任何负奇数将导致(与任何正奇数一样)n/2 向 -inf 逼近。恰好对于 n == -1,即 -1。
      【解决方案5】:

      如果你右移除以二,你总是会向下“四舍五入”——如果是正数,则趋近于零,如果是负数,则远离它。

      如果这不是你想要的,你可以纠正它:

      if (n & 1 > 0 && n < 0)
          result += 1;
      

      【讨论】:

      • +1: 但是你为什么要检查n &amp; 1 &gt;0。单独检查n &lt; 0 就足够了 这有效:int result=x&gt;&gt;1; if (x&gt;=0){ return result; } else { return ++result;}
      • @eagertoLearn:只有奇数的负整数会给出不想要的结果。
      【解决方案6】:

      在奇怪的情况下,两个运算都会在结果中产生一个取整运算。

      • 3/2 -> floor(1.5) => 1
      • -3/2 -> 楼层(-1.5) => -2

      你可以打个支票,比如\

      if ( isOdd(number) && isNegative(number) )
         result++;
      

      【讨论】:

      • 但这是不正确的。 (-3)/2-1 因为整数除法向零舍入,这与 floor 不同。
      猜你喜欢
      • 2013-06-08
      • 2010-11-16
      • 1970-01-01
      • 1970-01-01
      • 2015-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多