在 C 中使用 unsigned 并产生负临时结果

Using unsigned in C with negative temporary results

据我了解,在 C 中,unsigned 类型在整数环模 4294967296 中的行为类似于算术(假设 unsigned 具有 32 位长度)。

所以,从基本环理论看来,如果我们有一个涉及 * + - 的整数(现在我的意思是ℤ!)计算,那么无论临时发生什么样的上溢和下溢,只要最终结果在[0, 2^32] ?

范围内,最终结果就会正确

例如,在下面的计算中:

#include <stdio.h>

int main(void){

  unsigned v = 50000;
  unsigned w = 100000;
  unsigned x = 300000;
  unsigned y = 20000;
  unsigned z = 4000000000;

  unsigned r = v*w - x*y + z;

  printf("v*w - x*y + z = %u \n", r);

  return 0;
}

我们得到的临时结果与用 ℤ 计算得到的结果不同,但我们仍然得到正确答案。

这是对的还是会出什么问题?

根据C11 6.2.5 Types /9(我的重点):

A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

换句话说,如果类型是无符号的,并且您要使用的模数值是 UINT_MAX + 1,并且类型是正确的宽度(在这种特殊情况下,所有这些都是正确的),这将按预期工作。

但是,您应该正确地将常量指定为无符号,以确保常量本身没有溢出问题(在这种情况下,C 将使用更宽的有符号类型,因此,假设您可以分配更宽的类型对于 unsigned int,应该没有问题 - 例如,如果您没有使用二进制补码实现,则可能会出现问题,尽管那不太可能)。

您还应该使用类型 保证 足够大,因为 unsigned int 在某些平台上可能小于 32 位宽。

换句话说,您的陈述应采用以下形式:

unsigned long z = 4000000000U;

这当然意味着您可能需要进行 自己的 模数运算,因为 unsigned long 可能 更多 超过 32 位宽。

当然,如果您的平台提供它们,far 更优选使用固定宽度类型,在您的情况下 uint32_t。这样,你就得到了一个 Goldilocks 类型(既不太小也不太大,绝对是二进制补码))并且你可以让 C 自己处理模运算:

uint32_t z = 4000000000U;

是我想要的解决方案。