首页 > 解决方案 > 为什么是 numeric_limits::max() 不等于 -1?

问题描述

#include <iostream>
#include <cstdint>

using namespace std;

static_assert(-1 == numeric_limits<uint64_t>::max()); // ok
static_assert(-1 == numeric_limits<uint32_t>::max()); // ok
static_assert(-1 == numeric_limits<uint16_t>::max()); // error

int main()
{
    cout << numeric_limits<uint16_t>::max() << endl;
    cout << uint16_t(-1) << endl;
}

输出:

65535
65535

为什么 numeric_limits<uint16_t>::max() 不等于-1?

更新:

根据cppref

同样,USHRT_MAX 可能不是无符号类型:它的类型可能是 int。

标签: c++standardsnumericunsigned-integercompile-time-constant

解决方案


整数转换和促销

uint16_t值经过整数提升uint32_t,而对于(您的特定平台;见下文)uint64_t-1 (本身不是整数文字,而是应用于整数文字的一元减号运算符1)经过整数转换并得到结果值由于此转换的源值和目标值之间的整数一致性,这等于uint32_t和类型的各自的最大值。uint64_t

static_assert(-1 == std::numeric_limits<std::uint64_t>::max());
//            ^^
//            | Integer conversion:
//            |   Destination type: uint64_t
//            |   Resulting value: std::numeric_limits<std::uint64_t>::max()

static_assert(-1 == std::numeric_limits<std::uint32_t>::max());
//            ^^
//            | Integer conversion:
//            |   Destination type: uint32_t
//            |   Resulting value: std::numeric_limits<std::uint32_t>::max()

static_assert(-1 == std::numeric_limits<std::uint16_t>::max());
//                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                  | Integer promotion:
//                  |   Destination type: int
//                  |   Resulting value: std::numeric_limits<std::uint16_t>::max()

[expr.eq]/1[expr.eq]/6 [强调我的]:

[表达式.eq]/1

==等于)和!=(不等于)运算符从左到右分组。操作数应具有算术、枚举、指针或指向成员类型或类型的指针std​::​nullptr_­t。运算符==and!=都产生trueor false,即类型 的结果bool。在以下每种情况下,在应用了指定的转换后,操作数应具有相同的类型

[表达式.eq]/6

如果两个操作数都是算术**或枚举类型,则对两个操作数执行通常的算术转换true如果指定的关系是 true并且false如果是 ,则每个运算符都应让步false

[conv.integral]/1[conv.integral]/2

[conv.integral]/1

整数类型的纯右值可以转换为另一种整数类型的纯右值。无作用域枚举类型的纯右值可以转换为整数类型的纯右值。

[conv.integral]/2

如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模2n其中n是用于表示无符号类型的位数)。[ 注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。——尾注]

仅此一项就应该为所有三个示例产生相同的行为。但是,这种uint16_t情况的不同之处在于[conv.integral]/5适用:

[conv.integral]/5

作为积分促销允许的转换不包括在积分转换集中。

来自[conv.rank]/1

[转化排名]/1

每个整数类型都有一个整数转换等级,定义如下:

[...]

(1.3) 的等级long long int应大于 的等级, 的等级应大于long int等级int, 的等级应大于 的等级, 的等级short int应大于 的等级signed char

(1.4) 任何无符号整数类型的秩应等于相应有符号整数类型的秩。

uint16_t(相同或低于short int)的整数转换秩低于 的int,这意味着[conv.prom]/1适用于uint16_t[强调我的]:

[conv.prom]/1

如果可以表示源类型的所有值,则可以将除bool, char16_­t,之外的整数类型的纯右值char32_­t,或者wchar_­t 整数转换秩小于 的秩的int纯右值转换为类型的纯右值;intint否则,源纯右值可以转换为类型的纯右值unsigned int


平台相关行为

然而,虽然我们能够为uint16_t上述论证提出一个论点,因为对最大值的下限要求——unsigned short int保证uint16_t总是具有比整数转换等级更低的——我们不能为永远不会更低int的事实提出相反的论点整数转换等级高于,因为 ISO C++ 标准对基本整数类型的最大值要求没有上限。uint32_tint

[basic.fundamental]/2[basic.fundamental]/3 [摘录,强调我的]:

[basic.fundamental]/2

有五种标准有符号整数类型:“<code>signed char”、“<code>short int”、“<code>int”、“<code>long int”和“<code>long long int”。在此列表中,每种类型提供的存储空间至少与列表中它前面的类型一样多。[...] Plain具有执行环境架构所建议的自然int大小;提供其他有符号整数类型以满足特殊需要。

[basic.fundamental]/3

对于每个标准有符号整数类型,都存在相应的(但不同的)标准无符号整数类型:“<code>unsigned char”、“<code>unsigned short int”、“<code>unsigned int”、“<code >unsigned long int”和“<code>unsigned long long int”,每个占用的存储量和对应的有符号整数类型的对齐要求相同;[...]

有符号和无符号整数类型应满足 C 标准第 5.2.4.2.1 节中给出的约束

而且,来自C11 标准草案[摘录,强调我的]:

5.2.4.2.1 整数类型的大小<limits.h>

[...] 它们的实现定义值的大小(绝对值)应等于或大于所示值,且符号相同。

[...]

  • 类型对象的最大值short intSHRT_MAX +32767

  • 类型对象的最大值intINT_MAX +32767

[...]

请注意,这些最大值描述了相应基本整数类型应能够存储的最大值的下限,而对这些最大值的上限没有要求。此外,从上面的 [basic.fundamental]/2 引用中回想一下,每个后续的基本(有符号)整数类型只需要提供至少与处理它的那个(在列表中)一样多的存储空间。

这意味着,理论上,一个平台可以分别实现 32 位宽和 64 位宽的整数,这意味着在这个平台上,short int整数转换等级与 ( )相同,这意味着转换等级低于,在这种情况下,也适用于这个特定平台上的示例。intuint32_tunsignedshort intint[conv.prom]/1uint32_t


推荐阅读