在C语言中,如何计算一个无符号数的负值

6
在K&R ANSI C书中,第A.7.4.5节(一元负运算符)中指出:
“……无符号量的负值是通过从推广类型的最大值中减去推广值并加1来计算的;……”
这个计算方法是怎样的呢?你能给一个简短的C示例吗?
我不明白这如何得到例如200u的负值:从任何整数类型(有符号或无符号)的最大值中减去200并加1并不能得到-200。
我知道一元负运算符的作用——问题是我不明白根据描述如何计算结果。

1
显然,你不知道一元减运算符的作用。对于有符号操作数,你可能已经有了一个很好的想法。而你只是假设它对于无符号操作数的行为是类似的。你的假设是错误的。实际上,它在无符号操作数上的行为完全不同(至少在语言层面上是如此)。上述引用正确描述了无符号操作数的行为。 - AnT stands with Russia
可能是C:带有无符号操作数的一元减运算符行为的重复问题。 - Ciro Santilli OurBigBook.com
6个回答

9

无符号数值不能为负,因此-200不可能是一个结果。

这里所说的是如果您的系统 UINT_MAX 是65535,那么以下操作的结果:

unsigned a = 200;
unsigned b = -a;
long c = -a;

bc中都会留下65336。

如果您的系统中 UINT_MAX > LONG_MAX (通常是因为 intlong 的大小相同),则需要使用 long long 来获取 c 的值(尽管请注意,甚至没有任何保证它足够长)。

如果不理解这个细节(将无符号数取反的结果仍然是另一个必定是正的无符号数),那么可能会导致一些意想不到的效果。例如,在这段代码中,第一个例子打印"true",但第二个例子打印"false"

int a = 200;
unsigned b = 200;
if (-a < 100) printf("true\n"); else printf("false\n");
if (-b < 100) printf("true\n"); else printf("false\n");

(请注意,我们没有在任何地方存储否定运算符的结果 - 这不是问题所在。)

我不是在询问如何在无符号类型中存储负值。 - Ree
@Ree:你存储在哪里并不重要。当你对一个无符号值进行算术运算时,整个计算都将作为无符号计算执行。这意味着,在你的情况下,你永远不会得到负值。再次强调,-200u不是负数。你实际上可以将它存储在'long int'变量(有符号)中,仍然会得到一个值。 - AnT stands with Russia
谢谢AndreyT,我已经添加了“long”示例来澄清这个问题。 - caf

8
显然,您在引用的描述中错过了“unsigned”这个关键词。 在C语言中,“unsigned”数量的“负”仍然是unsigned,这意味着它实际上并不是负数。按定义,无符号值永远不能为负数。 C中无符号值的算术是模算术,或者简单地说,当对它们进行算术运算时,无符号量会“环绕”。 一元否定也不例外。计算无符号 n-n 和计算 0-n 没有区别。如果nunsigned int 并且其值为200,则预期结果不是-200,而是UINT_MAX-200 + 1 ,这正是引文告诉您的内容。

1
我标记您的答案为已接受,因为您指出了我的主要错误——我认为在评估后,“-200u”通过被提升为能够存储该值的有符号整数而成为有符号数量。 - Ree

6

它描述了实现模运算的操作,即计算一个值,使得

a + (-a) == 0

这使得取反的无符号数表现得很接近于取反的有符号数。
在采用二进制补码(如x86)的机器上,这是通过将无符号数的位模式视为普通有符号数,并使用机器的标准“取反”指令来完成的。

为什么这本书描述了二进制补码系统,而C语言也可以使用其他表示法呢?如果你有一个一的补码机器,结果将是-0(实际上就是0)。 - Ree
1
答案不正确。首先,引用谈论的是无符号值。对于无符号值,不存在所谓的“二进制补码”。有符号表示法的概念仅适用于有符号值。其次,引用中描述的行为是无符号值(模算术)的标准行为,与任何特定表示方式都没有任何关系。就我而言,机器可以是三进制的。 - AnT stands with Russia
1
AndreyT的评论非常准确,特别是对于这个条款对于非二进制整数类型的影响。 - Stephen Canon
它与二进制补码的唯一关系在于,在使用二进制补码的机器上,模算术纯粹是概念性的。编译器不需要任何努力,它只是自动地实现了这种方式(这正是你的答案用一个例子说明的)。然而,这只是一个实现细节,与语言规范没有任何形式上的联系。 - AnT stands with Russia
再次强调,通过您的汇编示例,您只是展示了在二进制补码机器上模算术是概念性的,并且不需要额外的代码。也就是说,编译器不需要关心值是有符号还是无符号(直到我们进行乘法/除法运算)。这是一个实现细节,在C语言层面上没有任何意义。 - AnT stands with Russia
显示剩余3条评论

4

另一个问题已经涉及到了这个主题

例子

unsigned char i = -10;
printf("%u\n",i);

结果

246

4
无符号整数类型的运算使用模算术。 模m的算术与常规算术大致相同,除了结果是除以m的正余数,如果您在学校没有遇到过(更多详细信息,请参见Wikipedia文章)。例如,7-3模10为4,而3-7模10为6,因为3-7为-4,并将其除以10得到商为-1,余数为6(它也可以用商为0和余数为-4表示,但在模算术中不是这样工作的)。模m的可能整数值是从0到m-1(包括m-1)的整数。负值不可能,-200在任何情况下都不是有效的无符号值。
现在,一元负号表示负数,这不是模m的有效值。在这种情况下,我们知道它在0到m-1之间,因为我们从无符号整数开始。因此,我们要将-k除以m。由于可能的一个值是商为0余数为-k,另一个可能的值是商为-1余数为m-k,因此正确答案是m-k。
C中的无符号整数通常由最大值而不是模数来描述,这意味着一个无符号16位数通常被描述为0到65535,或者具有最大值65535。这是通过指定m-1而不是m来描述值。
引用中所说的负值是通过从m-1中减去它然后加上1来获得的,因此-k是m-1-k+1,即m-k。描述有点绕,但它使用现有定义规定了正确结果。

3

让我们保持简单,看看一个无符号字符... 8位,值范围为0-255。

什么是(unsigned char)-10,它是如何计算的?

按照您引用的K&R声明,我们有:

-10的提升值为 10 从 提升类型的最大值减去是 255 加上 1 = 246

所以(unsigned char)-10实际上是246。 这有意义吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接