将有符号整数值转换为可排序的无符号C++的标准兼容方法是什么?
What is the standard compliant way to convert a signed integral value to a sortable, unsigned in C++?
我有一个用例,我需要将有符号值转换为无符号值,以使值可排序。 我需要这个来char
、short
、int
、long
和long long
通过可排序,我的意思是对于signed
类型 X,如果(a < b)
则转换为无符号converted(a) < converted(b)
。 请注意,在许多情况下,从负signed
值直接转换为unsigned
值将使该值大于0
并破坏此约束(二进制补码实现(
对于char
来说,最简单的想法是:
unsigned char convert(char x)
{
return (unsigned char)(x ^ 0x80); // flip sign to make it sortable
}
但这似乎undefined behavior
.
虽然可以转换为更大的类型,添加类型 MIN 值,然后转换为 unsigned
类型,但我不确定这是否更合规,并且不适用于long long
如何在没有任何undefined behavior
的情况下完成此操作?
使用 memcpy
进行转换似乎是安全的,但不清楚如何以兼容的方式维护排序顺序。
(请注意,这类似于:没有兼容的方式来转换相同大小的有符号/无符号,除非我需要结果保持排序顺序(
你做错了,因为实际上并没有定义翻转有符号值的符号位。
让我们使用两位类型:
00 01 10 11 Order for unsigned 0 1 2 3
10 11 00 01 Order for 2s complement -2 -1 0 1
11 (10 00) 01 Order for sign-magnitude -1 (-0 +0) 1
10 (11 00) 01 Order for 1s-complement -1 (-0 +0) 1
您要做的是转换为无符号(始终定义为值保留,带有环绕(,然后添加偏差,使最负数变为 0:
int x = whatever;
unsigned r = (unsigned)x - (unsigned)INT_MIN;
请注意:未定义有符号溢出,因此我们避免使用有符号类型。
当然,如果无符号类型的值比有符号类型的值少,这无济于事,这通常是允许的,尽管不是char
。
如果您想将负 0 保留为负数,则需要特别小心。
如果您想保持完全可移植性,这是不可能的。
指定unsigned int
的范围只是为了至少涵盖 int
的非负值。该标准允许在UINT_MAX == INT_MAX
.这同样适用于所有其他非固定宽度整数类型。
鉴于unsigned int
的范围可能小于int
的范围,鸽子洞原则适用:除非unsigned int
可以存储至少与int
一样多的不同值,否则您无法将所有int
值重新分配给相应但不同的unsigned int
值。
引用N4140(大约C++14(:
3.9.1 基本类型
1 [...]对于窄字符类型,对象表示形式的所有位都参与值表示形式。对于无符号窄字符类型,值表示形式的所有可能的位模式都表示数字。这些要求不适用于其他类型的类型。[...]
3 对于每个标准有符号整数类型,存在相应的(但不同的(标准无符号整数类型:"
unsigned char
"、"unsigned short int
"、"unsigned int
"、"unsigned long int
"和"unsigned long long int
",每个类型占用相同的存储量,并具有与相应的有符号整数类型相同的对齐要求47;即,每个有符号整数类型 具有与其相应的无符号整数类型相同的对象表示形式。[...]有符号整数类型的非负值范围为 对应无符号整数类型的子范围,每个对应有符号/无符号类型的值表示形式应相同。[...]
这保证了您不会遇到问题 unsigned char
.unsigned char
不可能有任何填充位。unsigned char
拥有填充位是没有意义的:给定unsigned char c;
,您将如何访问这些填充位? reinterpret_cast<unsigned char &>(c)
?这显然只是给你c
.唯一类似于填充位的unsigned char
是程序完全透明的东西,例如当使用 ECC 内存时。
对于所有其他非固定宽度的整数类型,从short
到long long
,"子范围"的标准含义允许相等的范围。
我想我依稀记得读到过,可能有古老的 CPU 不提供任何本机无符号操作。这将使实现正确实现无符号除法变得非常棘手,除非它们声明无符号类型的可能签名位将被视为填充位。这样,他们可以简单地将 CPU 的有符号除法指令用于有符号或无符号类型。
若要保持所需的顺序,必须向所有值添加相同的数量,以便
a( 它们的相对差异不变,并且
b( 所有负值都转换为非负值。
添加一致的数量是执行此操作的唯一方法。 如果要排序的所有值最初都是相同的有符号类型 T,则为确保任何负值变为非负值而添加的数量必须是"-numeric_limits::min(("或者换句话说,您必须减去最小有符号值,该值为负数。
如果要将不同类型的类型引入到同一排序中(例如,对字符值以及 short、int、long 等进行排序(,则可能需要第一步转换为您将处理的最大有符号类型。 从较小的签名类型到较大的签名类型不会丢失信息。
为了避免溢出问题,我建议有条件地进行转换(即减去最小值(。
if (值 <0(
通过首先减去最小值(设为非负数(进行转换,然后转换为无符号类型(现在完全安全(
还
首先将已经非负值转换为无符号类型(完全安全(,然后添加与正值相同的调整,即添加 numeric_limits::max((+1
两者的 T 是原始签名的 T。 表达式 "numeric_limits::max((+1" 可以计算并转换为新的目标类型一次,然后在类型 newT 中用作常量。
我会从每个值中减去numeric_limits<T>::min()
。 这将保留您想要的排序属性,如果基础表示是 2 的补码(即,唯一的理智表示,以及实际上每个非博物馆居民计算机使用的表示(将执行您的期望,包括当输入值等于最负或最正的可表示整数时的边界情况 - 提供编译器使用SUB
指令,而不是ADD
指令(因为正值-numeric_limits<T>::min()
太大而无法表示(。
是否符合此标准? 不知道。 我的猜测是:应该不会。 如果您知道,请随意编辑。
公式x-(unsigned)INT_MIN
将在所有UINT_MAX > INT_MAX
的计算机上产生合适的排名。 对于任何一对有符号整数 x 和 y,其中 x>=y,(无符号(x-(无符号(y 将等于 x-y 的数值;所以如果 y 是INT_MIN,那么 x>=y 对于所有 x,前面提到的公式将报告 x 大于 INT_MIN 的量,这当然与 x 的排名相同。
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- 如何从Windows应用程序输出到标准?
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 标准::变体的赋值运算符
- 捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件
- 如何在 Mac 上使用 c++17 并行标准库算法?
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- 并行标准::复制复杂性