为什么这个长溢出为-1,而不是类型的最小值?

时间:2015-07-09 20:12:17

标签: java integer biginteger integer-overflow

我有以下代码,当完整的二进制树为layer层高时,它返回树中的节点数:

public static long nNodesUpToLayer(int layer) {
        if (layer < 0) throw new IllegalArgumentException(
            "The layer number must be positive: " + layer );

        //At layer 0, there must be 1 node; the root.
        if (layer == 0) return 1;

        //Else, there will be 1 + 2 * (the number of nodes in the previous layer) nodes.
        return 1 + (2 * nNodesUpToLayer(layer - 1));

奇怪的是,当我将63输入函数(产生此函数的最小值)时,它会返回-1。在62,它会返回9223372036854775807,因此这似乎是由溢出引起的。

它不应该给我回Java长期的最小值+它溢出的数量吗?无论我给出的输入是什么(通过62),它总是会返回-1,而不是一个看似随机的数字,我期望从溢出中获得。

我不完全确定如何调试它,因为它是递归的,并且只有在函数达到基本情况时才会评估我感兴趣的值。

3 个答案:

答案 0 :(得分:57)

你是对的,它是64位有符号整数的溢出错误。它转到-1而不是最小整数值的原因是因为你加倍,而不是简单地添加一个。

Two's Complement中的

9223372036854775807为63 1 s:

0111 1111 ... 1111 1111

要以二进制加倍,只需执行左移:

1111 1111 ... 1111 1110

但是,在Two's Complement中的这个数字不是9223372036854775807的两倍,而是-2。然后,当然,您需要先添加1,然后才能返回-1结果。

答案 1 :(得分:15)

实际上,它会返回正确的金额。只是“它溢出的数量”完全正确的答案-1:)

考虑一下:
对于2^n - 1层,完整二叉树中的节点数为n。因此,它的二进制表示为0000...00111...111,其中1 s的数量恰好是层数减1.只要达到long的长度,就会被截断11...11,正是-1

答案 2 :(得分:10)

我总是喜欢用这样的东西进行可视化。

                      (min long)
                      v 
<--------------------||--------------------------------------->
                     ^                                   ^
               (max long, n)                            -1

n的位置是9223372036854775807 - 在乘以2之前的值。而不是乘法,而是将其视为加法。 n + n。通过在数字线上看到它,你可以看到你最终在-2。你基本上已经超过了大多数负数。

因此,与其他人相比,我的答案提供了一些有意义的东西,在这种情况下,一个有用的工具是将算术分成多行以便进行调试。你可以写:

int a = nNodesUpToLayer(layer - 1);
int b = 2 * a;
int c = 1 + b;
return c;

你基本上按照你的预期强制执行操作顺序(这可能会帮助你意识到程序按照你想要的顺序执行操作),但是它也允许你进入调试器并且查看计算的中间值。在这里您会注意到b == -2。为什么b == -2?好吧,一定是因为2 * a == -2等等。