【问题标题】:Why is this long overflowing to -1, instead of the minimum value for the type?为什么这个 long 溢出到 -1,而不是类型的最小值?
【发布时间】:2015-10-17 11:00:53
【问题描述】:

当完整的二叉树为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 的 long 的最小值 + 溢出的数量还给我吗?不管我给它什么输入(通过62),它总是会返回-1,而不是我期望溢出的看似随机的数字。

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

【问题讨论】:

  • 我会离开 this 这里然后走开......
  • @Snowman 感谢您的建议。现在我知道一棵高度为1000 的树将有21430172143725346418968500981200036211228096234110672148875007767407021022498722449863967576313917162551893458351062936503742905713846280871969155149397149607869135549648461970842149210124742283755908364306092949967163882534797535118331087892154125829142392955373084335320859663305248773674411336138751 节点。我真的很惊讶它计算得如此之快。我预计 BigInteger 会引入大量开销(或者我听说过),但这几乎是立即完成的。
  • 这个函数在技术上是2^(layer+1) - 1,那你为什么要写一个递归函数而不是简单的return (1L &lt;&lt; (layer+1)) - 1呢?这快了几十或几百倍。使用该公式也更容易看出为什么它在 layer = 63 处溢出
  • 正如我上面所说,它真的只是pow(2, layer) - 1
  • 不,我没有理由拒绝它。但就我个人而言,单行表达式比长函数更清晰

标签: java integer biginteger integer-overflow


【解决方案1】:

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

Two's Complement 中的9223372036854775807 是 63 1s:

0111 1111 ... 1111 1111

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

1111 1111 ... 1111 1110

但是,Two's Complement 中的这个数字不是9223372036854775807 的两倍,而是-2。然后,当然,您在返回之前添加 1 以获取您的 -1 结果。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 我喜欢这个答案,因为它非常清楚初始 -1 是如何产生的,顺便说一下,它不依赖于所使用的整数类型的大小。但是,一旦达到 -1,递归就不再计算树的大小,因此我认为您不能将此参数扩展到后续情况。相反,在这些情况下,很明显递归只是重复计算 1 + ( 2 * -1 )。
    【解决方案3】:

    我总是喜欢这样的可视化。

                          (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等等。

    【讨论】:

    • 在您展示可视化之前,我一无所知,无法理解其他答案的任何内容!谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2016-04-15
    • 2021-12-15
    • 2016-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多