【问题标题】:Why is (18446744073709551615 == -1) true?为什么 (18446744073709551615 == -1) 是真的?
【发布时间】:2017-03-29 05:36:14
【问题描述】:

当我在 string::npos 工作时,我注意到了一些东西,但我在网络上找不到任何解释。

(string::npos == ULONG_MAX)

(string::npos == -1)

是真的。

所以我尝试了这个:

(18446744073709551615 == -1)

这也是正确的。

这怎么可能?是因为二元对话吗?

【问题讨论】:

  • 溢出:p 你比较一个无符号和有符号的值
  • 这不是未定义的行为。
  • 18446744073709551615 = 2^64 -1 ... 诡异的巧合?
  • @George 它不是 UB。
  • 这是实现定义的行为。这个问题并不正确; (18446744073709551615 == -1) 并不总是正确的。

标签: c++ equality unsigned signed integer-overflow


【解决方案1】:

18,446,744,073,709,551,615

提到的这个数字,18,446,744,073,709,551,615,实际上是2^64 − 1。这里重要的是2^64-1本质上是基于0的2^64。无符号整数的第一位是0,而不是1。所以如果最大值是1,它有两个可能的值:0,或者1(2)。

让我们看看2^64 - 1在64位二进制中,所有位都打开了。

1111111111111111111111111111111111111111111111111111111111111111b

-1

让我们看一下+1 的 64 位二进制文​​件。

0000000000000000000000000000000000000000000000000000000000000001b

为了使其在 One's Complement (OCP) 中为负,我们将位反转。

1111111111111111111111111111111111111111111111111111111111111110b

计算机很少使用 OCP,它们使用 Two's Complement (TCP)。要获取 TCP,请向 OCP 添加一个。

1111111111111111111111111111111111111111111111111111111111111110b (-1 in OCP)
+                                                              1b (1)
-----------------------------------------------------------------
1111111111111111111111111111111111111111111111111111111111111111b (-1 in TCP)

“但是,等等”你问,如果在 Twos Complement -1 中,

1111111111111111111111111111111111111111111111111111111111111111b

并且,如果在二进制中 2^64 - 1

1111111111111111111111111111111111111111111111111111111111111111b

那么他们是平等的!而且,这就是你所看到的。您正在将有符号的 64 位整数与无符号的 64 位整数进行比较。在 C++ 中,这意味着将有符号值转换为无符号值,编译器会这样做。

更新

对于thanks to davmac in the comments 的技术更正,从-1(即signed)到相同大小的unsigned 类型的转换实际上是在语言中指定的,而不是架构的功能。总而言之,您可能会发现上面的答案对于理解支持双语恭维但缺乏规范以确保您可以依赖的结果的架构/语言很有用。

【讨论】:

  • 严格来说,转换前两个数字的位表示是否相同并不重要。即使使用 1 的补码或有符号幅度表示,(有符号)-1 到 unsigned long 的转换也将始终导致 ULONG_MAX。 (当然, 转换后的位模式将是相同的)。
  • 此答案中的所有 1 和 0 都是实现定义的(-1 除外)。在 C++ 中,它们如何用二进制表示也无关紧要。
  • @davmac 怎么来的? ULONG_MAX==2^32-1 而~1==2^32 -2。在一个补码系统上 -1==~1==(ULONG_MAX-1)。
  • @Red.Wave ~1==(ULONG_MAX-1) 不正确。它们可能具有与一个补码表示相同的表示,但不具有相同的值。从 -1 到 unsigned long 的转换由语言指定以生成 ULONG_MAX。因此,如果 -1==~1(如在一个补码中)则 ~1==ULONG_MAX,因为该比较应用通常的算术转换,它将 -1 转换为 unsigned long(产生 ULONG_MAX)。
  • @Red.Wave 假设 nop 转换不正确。 C99 6.3.1.3,“否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内”
【解决方案2】:

string::npos 被定义为 constexpr static std::string::size_type string::npos = -1;(或者如果它在类定义中定义为 constexpr static size_type npos = -1;,但这真的无关紧要)。

转换为无符号类型的负数的环绕(std::string::size_type 基本上是 std::size_t,它是无符号的)由标准完美定义。 -1 包装为无符号类型的最大可表示值,在您的情况下为 18446744073709551615。请注意,确切的值是实现定义的,因为std::size_t 的大小是实现定义的(但能够容纳相关系统上可能的最大数组的大小)。

【讨论】:

    【解决方案3】:

    根据 C++ 标准(文档编号:N3337 或文档编号:N4296)std::string::npos 定义如下

    static const size_type npos = -1;
    

    其中 std::string::size_type 是一些无符号整数类型。所以 std::string::npos 等于 -1 没有什么好说的。初始化器被转换为std::string::npos的类型。

    至于这个方程

    (string::npos == ULONG_MAX) is true,
    

    那么这意味着std::string::npos 类型在使用的实现unsigned long 中有类型。此类型通常对应于类型size_t

    在这个等式中

    (18446744073709551615 == -1)
    

    左边的文字有一些无符号整数类型,适合存储这么大的文字。因此,右操作数也通过传播符号位转换为这种无符号类型。由于左操作数表示自身类型的最大值,因此它们相等。

    【讨论】:

    • 根据 C++ 语言标准(至少根据 open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf 第 2.14.2 节的 C++11),左侧文字必须具有 signed 类型,而不是 无符号类型。在没有合适的有符号类型的情况下,将其转换为无符号类型的编译器似乎是对语言的扩展。
    【解决方案4】:

    这都是关于有符号溢出和负数存储为 2s 补码的事实。这意味着要获得负数的绝对值,请将所有位反转并加一。这意味着在进行 8 位比较时,255 和 -1 具有相同的二进制值 11111111。这同样适用于更大的整数

    https://en.m.wikipedia.org/wiki/Two%27s_complement

    【讨论】:

    • 负数不存储为定义/标准的 2s 补码。他们只会那样做。
    • 这里没有签名溢出。
    • 这取决于 CPU 架构,但大多数现代架构使用 2s 补码
    • 这与 CPU 架构无关,因为它由 C++ 标准明确定义。
    猜你喜欢
    • 1970-01-01
    • 2011-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多