【问题标题】:Fail to Understand Java Two's Complement无法理解 Java Two 的补码
【发布时间】:2014-06-18 15:34:50
【问题描述】:

我有这个代码:

package com.company;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class Main {

    private final static int broadcast = 0xffffffff;    //4294967295, or 255.255.255.255
    private final static int firstClassE = 0xf0000000;  //4026531840, or 240.0.0.0

    public static int GetIntInetAddress(InetAddress toConvert)
    {
        final byte[] addr = toConvert.getAddress();

        final int ipAddr =
                ((addr[0] & 0xFF) << (3 * 8)) +
                        ((addr[1] & 0xFF) << (2 * 8)) +
                        ((addr[2] & 0xFF) << (1 * 8)) +
                        (addr[3] & 0xFF);

        return ipAddr;
    }

    public static Boolean IsClassEAddress(InetAddress address)
    {
        int curAddr = GetIntInetAddress(address);
        Boolean test1 = curAddr >= firstClassE;
        Boolean test2 = curAddr < broadcast;
        System.out.println(String.format("\ncurAddr: %s, firstClassE: 240.0.0.0, broadcast: 255.255.255.255", address.getHostAddress()));
        System.out.println(String.format("curAddr: %d, firstClassE: %d, broadcast: %d, curAddr >= firstClassE: %s, curAddr < broadcast: %s",
                                         curAddr, firstClassE, broadcast, test1 ? "true" : "false", test2 ? "true" : "false"));
        return (test1 && test2) ? true : false;
    }

    public static void main(String[] args) throws UnknownHostException
    {
        if (IsClassEAddress(InetAddress.getByName("1.0.0.0")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
        if (IsClassEAddress(InetAddress.getByName("250.0.0.0")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
        if (IsClassEAddress(InetAddress.getByName("239.255.255.255")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
        if (IsClassEAddress(InetAddress.getByName("240.0.0.0")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
        if (IsClassEAddress(InetAddress.getByName("240.0.0.1")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
        if (IsClassEAddress(InetAddress.getByName("255.255.255.255")))
        {
            // Raise a flag
            System.out.println("Class E IP address detected.");
        }
    }
}

产生以下输出:

curAddr: 1.0.0.0, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: 16777216, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: true, curAddr < broadcast: false

curAddr: 250.0.0.0, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: -100663296, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: true, curAddr < broadcast: true
Class E IP address detected.

curAddr: 239.255.255.255, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: -268435457, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: false, curAddr < broadcast: true

curAddr: 240.0.0.0, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: -268435456, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: true, curAddr < broadcast: true
Class E IP address detected.

curAddr: 240.0.0.1, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: -268435455, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: true, curAddr < broadcast: true
Class E IP address detected.

curAddr: 255.255.255.255, firstClassE: 240.0.0.0, broadcast: 255.255.255.255
curAddr: -1, firstClassE: -268435456, broadcast: -1, curAddr >= firstClassE: true, curAddr < broadcast: false

我不明白为什么数字和比较不是我期望的那样,但代码却产生了我想要的结果。我认为这是整个二进制补码的东西,出于某种原因我只是没有“得到”。机械地,我知道它(二进制补码)是翻转位并加 1,但我没有得到的是,如果某些数字被反转,为什么我的比较工作正常?

例如,在第一次检查 IP 1.0.0.0 时,检查 int 值 16777216 是否小于 255.255.255.255 的 int 值,即 -1。结果是假的,但是广播IP,当转换为int时,显然比1.0.0.0的IP更大,而不是更小。同样,检查 1.0.0.0 是否至少或高于 240.0.0.0 会返回 true,但我们清楚地知道情况并非如此。

我检查了边界情况,一切正常……我只是不明白为什么会这样(我写了代码,所以去想吧!)。如果有更明确的方法来确定 IP 是否在某个范围内,我想探索一下,因为我的方法一定没有意义,尽管可以工作(或者可以吗?)

在 IntelliJ 中,这种行为还有另一个奇怪的例子。当我检查一个地址时,检查器显示正确的值和负值,如下图中我用红色箭头突出显示的那样。使用 Windows calc,我输入 -84 并转换为十六进制并收到 FFF...FAC。当我输入 172 时,我只收到了 AC...为什么我得到相同的十六进制数字,只是在最 sig 位置前面有一个 1?

更新

感谢所有耐心的讨论和出色的答案!我想我了解了事物的机制,但仍在努力解决使用的微妙之处。 :) 干杯!

【问题讨论】:

  • 那是一大堵代码和输出。你能不能提供一个小小的 sn-p 来证明你对二进制补码的误解?
  • "广播 IP,当转换为 int 时,显然比 1.0.0.0 的 IP 更大,而不是更小" -- 这一点都不“明显”。二进制中的1.0.0.00x01000000255.255.255.2550xFFFFFFFF。第一个值代表16777216 ,第二个值代表-1。你不能通过说你没有“得到”二进制补码来改变这一点。就是这样。
  • 如果你想让比较正常工作,最简单的方法是使用long 而不是int,然后一切都会是肯定的。
  • return (test1 &amp;&amp; test2) ? true : false;trololo
  • 0xFFFFFFFF0x01000000“大”仅当比较无符号时。作为已签名的Javaint,它更小。

标签: java int twos-complement


【解决方案1】:

在 32 位二进制补码数中,非负整数为 0x00000000 - 0x7fffffff,它们以普通方式从十六进制转换为十进制。

最低ie最负数)在机器中表示为0x80000000ie最高位,没有其他位已设置)。设置这个位的真正含义是将低 31 位表示的正整数与数字 -(0x80000000) 相加。

练习32 位二进制补码中的数字-84 是多少? 既然是负数,我们必须设置高位。所以我们从-(0x80000000) = -2147483648开始。现在您可以用代数方式求解其他 31 位需要是什么:

-2147483648 + x = -84
=> x = 2147483648 - 84
=> x = 2147483564
=> x = 0x7fffffac

很明显,如果您使用0x80000000 | 0x7fffffac,您将获得0xffffffac

也应该很明显0x7fffffac = (0x7f000000 + 0xff0000 + 0xff00 + 0xac) 和括号中的表达式等价于(2130706432 + 16711680 + 65280 + 172)。低位字节等价于十进制172这一事实在不考虑其他3个字节的情况下是没有意义的。

您的比较

现在您提到您的比较“不是您期望的那样”。我不知道你期望它们是什么,因为你没有解释,但我猜你想要一个true 范围内的 IP 地址的返回值 [240.0.0.0 .. 255.255.255.255) 不包括右侧范围。这意味着[0xf0000000 .. 0xffffffff) 范围内的 IP 地址不包括在内。

如果这些是 32 位二进制补码整数,则这是[-268435456, -1) 的范围,因此(0xf0000000 &lt;= ip_addr &amp;&amp; ip_addr &lt; 0xffffffff) 形式的任何表达式都会为您提供正确的结果。

由于您的函数返回上述表达式的更复杂的版本,因此它给出了正确的结果。

顺便问一下,你熟悉boolean 类型吗?您的方法是否有某些原因需要返回盒装原语(ie Boolean)?

【讨论】:

  • 那么,数字从 0 到最正到最负到 -1?如0,1,2..127,-128,-127..-1?计算机认为 -128 紧跟在 +127 之后,而 -1 是对序列中最大数字的完全合理解释(即:所有位都设置的数字)?难怪电脑这么乱,连一条直线都数不过来!哈哈待定...
  • 续。那么,编写这些比较的正确方法是什么?如果我将 first 设置为 120,last 设置为 -120,curr 设置为 125。检查将排除,因为 first (120)
  • @Jon,你是对的,如果你这样做byte b = Byte.MAX_VALUE; ++b;,那么表达式Byte.MIN_VALUE == b 将是true。短裤,整数,多头也一样。但是,我认为您的第二条评论过于复杂。我的回答向您解释了您的比较有效,因为您关心的 IP 地址范围是一个连续的负数范围。如果您关心一个 IP 地址范围,其中一端是 w.x.y.z128 &lt;= w,另一端是 a.b.c.da &lt; 128,那么您可能有两个不连续的范围,需要更谨慎的方法。跨度>
  • @Jon,顺便说一句,如果您觉得您的问题已得到解答,请随时接受提供的答案之一。
  • 我更多地考虑我会越过0x7fffff -> 0x80000000 边界的情况。暂时忘记 IP,假设我想检查一个数字是否落入0x7fffffff0xa0000000 的范围内,样本号为0x90000000。通过下面@jimgarrison 提案的第一部分运行,我有0x90000000 &amp; 0x7fffffff = 0x10000000。测试失败。那么我是否必须将比较分成两个测试,一个从最低 + 边界到最高 + 值,另一个测试从最小值到最高 - 边界?
【解决方案2】:

有几种方法可以确保正确比较无符号 32 位整数:

1) 使用 Java 8 中添加的Integer.compareUnsigned

2) 使用long 而不是int。如果该值构造正确,结果将是 0 到 232-1 范围内的正整数,然后可以毫无问题地进行比较。您必须注意强制转换和按位操作发生的顺序,以免符号扩展。这有效:

    final long ipAddr =
            (((long)addr[0] & 0xFF) << (3 * 8)) +
            (((long)addr[1] & 0xFF) << (2 * 8)) +
            (((long)addr[2] & 0xFF) << (1 * 8)) +
            ((long)addr[3] & 0xFF);

当每个byte 被强制转换为long 时,它会进行符号扩展,但&amp; 0xFF 会将使用符号扩展添加的任何1 位归零。

3) 编写一个 compare 方法,或 lessThanlessThanOrEqual 或任何你需要的,来解释符号。当参数有不同的符号时,这样的方法必须有特殊情况。例如:

static int compareUnsigned(int x, int y) {
    if (x >= 0 && y < 0) {
        return -1;        // y is actually greater than x if unsigned
    }
    else if (x < 0 && y >= 0) {
        return 1;         // x is greater than y if unsigned
    } else {
        return Integer.compare(x, y);
    }
}

或者,更简洁地说,

static int compareUnsigned(int x, int y) {
    if (((x ^ y) & 0x80000000) != 0) {  // see if x and y have different signs
        return (x < 0) ? 1 : -1;     
    } else {
        return Integer.compare(x, y);
    }
}

实际上,Java 8 源码中是这样完成的:

static int compareUnsigned(int x, int y) {
    return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE);
}

我完全没有想到。

【讨论】:

    【解决方案3】:

    你是对的,这是一个二进制补码的问题,我只能建议你阅读它并最终得到它 - 它会在各种整数溢出问题中一次又一次地咬你(它甚至在一些核心 Java 库)。

    问题本身是在 Java Integer 中保存从 -(2^31) 到 (2^31)-1 的数字。 255*255*255*255 的值为 2^32 - 1。它不可表示为有符号整数。因此,当您将 IP 转换为 signed int 时,您不会得到(对您而言)合理的值。如果您想将 IP 与内置的“小于”进行比较,请使用 IP 实际适合的原语 - 例如 long

    第二部分也发生了同样的事情。签名的byte 保存从-128 到127 的值。您将255 放在那里。你为什么期待一个理智的结果?现在,如果你使用无符号算术,其中字节包含 0..255,一切正常。

    【讨论】:

      【解决方案4】:

      总而言之,如果地址介于240.0.0.0255.255.255.254 之间,您希望IsClassEAddress(InetAddress address) 返回true。这需要 2 行代码:

      public static Boolean IsClassEAddress(InetAddress address)
      {
          int curAddr = GetIntInetAddress(address);
          return ((curAddr & 0xF0000000) == 0xF0000000) && (curAddr != 0xFFFFFFFF);
      }
      

      【讨论】:

      • 这很干净。上面显示的我的函数是冗长的,但我已经扩展了它,以便我可以检查每个测试的结果。但是,您的测试更有意义。不过,我确实有一个问题,上面提出了。假设我的范围跨越了最高 + 值 -> 最低 - 值边界。如果我通过上述运行一个应该在该范围内的数字,它将失败。这是意料之中的吗?
      • 例如,假设我想检查一个数字是否落入0x7fffffff0xa0000000 的范围内,样本编号为0x90000000。通过上述测试的第一部分运行它,我有0x90000000 &amp; 0x7fffffff = 0x10000000
      • 其他范围似乎也分崩离析。假设我有 0x30000000 作为下限; 0x40000000 & 0x30000000 = 0。0x500000 & 0x30000000 = 0x10000000。
      • 这种类型的掩码仅在范围边界以二进制表示为一定数量的 1 位后跟所有零位时才有效,就像网络掩码的情况一样。对于任意范围,它不能只使用掩码。如果您想使用任意范围,请将您的无符号屏蔽字节放入 long
      • 哦,好吧,所以我需要在最重要的位上加上 1 才能使这种简单的掩码正常工作,对吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-14
      • 2022-09-23
      • 1970-01-01
      相关资源
      最近更新 更多