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

时间:2015-11-06 14:43:04

标签: c++ casting language-lawyer

我有一个用例,我需要将有符号值转换为unsigned,以使值可排序。我需要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类型,我不确定这是否更符合,并且不适用于{ {1}}

如果所有类型都没有long long,怎么办呢?

使用undefined behavior转换似乎是安全的,但目前尚不清楚如何以合规的方式维护排序顺序。

(注意,这类似于:No compliant way to convert signed/unsigned of same size,除了我需要维护排序顺序的结果)

5 个答案:

答案 0 :(得分:19)

你做错了,因为实际上没有定义有符号值的翻转符号位。

让我们使用两位类型:

          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保留为负数,你需要特别小心。

答案 1 :(得分:13)

如果您想保持完全便携,这是不可能的。

unsigned int的范围仅指定为至少涵盖int的非负值。该标准允许实现UINT_MAX == INT_MAX。这同样适用于所有其他非固定宽度的整数类型。

鉴于unsigned int的范围可能小于int的范围,所以适用的原则是:您无法将int的所有值重新分配到相应但不同的值unsigned int,除非unsigned int可以存储至少与int一样多的不同值。

引用N4140(大致是C ++ 14):

  

3.9.1基本类型[basic.fundamental]

     

1 [...]对于窄字符类型,对象表示的所有位都参与值表示。对于无符号窄字符类型,值表示的所有可能位模式表示数字。这些要求不适用于其他类型。 [...]

     

3对于每个标准有符号整数类型,存在相应的(但不同的)标准无符号整数类型:&#34; unsigned char&#34;,&#34 ; unsigned short int&#34;,&#34; unsigned int&#34;,&#34; unsigned long int&#34;和&#34; unsigned long long int& #34;,每个存储占用相同的存储量,并且具有与对应的有符号整数类型 47 相同的对齐要求(3.11);也就是说,每个有符号整数类型   具有与其对应的无符号整数类型相同的对象表示。 [...] 有符号整数类型的非负值范围是a   对应的无符号整数类型的子范围,每个对应的有符号/无符号类型的值表示应相同。 [...]

这可以保证您不会遇到unsigned char的问题。 unsigned char不可能有任何填充位。 unsigned char有填充位是没有意义的:给定unsigned char c;,您将如何访问这些填充位? reinterpret_cast<unsigned char &>(c)?这显然只是给你c。与unsigned char可能的唯一类似于填充位的东西是对程序完全透明的东西,例如当使用ECC内存时。

对于所有其他非固定宽度整数类型,从shortlong long,&#34; subrange&#34;的标准含义允许相等的范围。

我想我模糊地回忆一下,可能有一些古老的CPU没有提供任何原生的无符号操作。这将使实现正确实现无符号除法变得非常棘手,除非它们声明无符号类型的将要符号位将被视为填充位。这样,他们可以简单地将CPU的带符号除法指令用于有符号或无符号类型。

答案 2 :(得分:3)

要保持所需的顺序,您必须为所有值添加相同的金额,例如

a)他们的相对差异没有变化

b)所有负值都变为非负值。

添加一致数量是唯一的方法。如果您要排序的所有值最初都是相同的有符号类型T,那么要添加以确保任何负值变为非负值的数量必须为 &#34; -numeric_limits ::分钟()&#34;换句话说,你必须减去最小的有符号值,这是负数。

如果要将不同类型引入相同的排序(例如,将char值与short,int,long等一起排序),您可能希望将第一步转换为最大的签名你会处理的类型。从较小的签名类型到较大的签名类型,不会丢失任何信息。

为了避免溢出问题,我建议进行转换(即减去最小值)有条件

if(value&lt; 0)

通过先减去最小值(使非负值)然后转换为无符号类型(现在完全安全)进行转换

否则

首先将已经非负的值转换为无符号类型(完全安全),然后将相同的调整添加为正值,即添加numeric_limits :: max()+ 1

两者的T是原始签名的T.表达式&#34; numeric_limits :: max()+ 1&#34;可以计算并转换为新的目标类型一次,然后在newT类型中用作常量。

答案 3 :(得分:2)

我会从每个值中减去numeric_limits<T>::min()。这保留了你想要的排序属性,如果底层表示是2的补码(即唯一的理智表示,并且实际上是每个非博物馆驻留计算机使用的那个)将做你期望什么,包括输入值等于最负或最正可表示整数的边界情况 - 提供编译器使用SUB指令,而不是{ {1}}指令(因为正值ADD太大而无法表示)。

此标准是否合规?不知道。我的猜测是:可能不是。如果您知道,请随时编辑。

答案 4 :(得分:2)

公式x-(unsigned)INT_MIN将在UINT_MAX > INT_MAX的所有计算机上产生合适的排名。对于任何一对有符号整数x和y,其中x> = y, (无符号)x-(无符号)y将等于x-y的数值;所以如果是的话 对于所有x,INT_MIN,然后x> = y,并且前面提到的公式将报告x大于INT_MIN的量,其当然与x相同。