将有符号整数值转换为可排序的无符号C++的标准兼容方法是什么?

What is the standard compliant way to convert a signed integral value to a sortable, unsigned in C++?

本文关键字:标准 C++ 无符号 是什么 方法 排序 整数 符号 转换      更新时间:2023-10-16

我有一个用例,我需要将有符号值转换为无符号值,以使值可排序。 我需要这个来charshortintlonglong 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 内存时。

对于所有其他非固定宽度的整数类型,从shortlong 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 的排名相同。