C++ 模数需要两个 *un*signed 字节之间的减法运算才能工作,为什么?

C++ modulus requires cast of subtraction between two *un*signed bytes to work, why?

以下Arduino (C++)代码

void setup()
{
  Serial.begin(115200);
  byte b1 = 12;
  byte b2 = 5;
  const byte RING_BUFFER_SIZE = 64;

  byte diff = b2 - b1;
  byte diff2 = (byte)(b2 - b1) % RING_BUFFER_SIZE; //<---NOTE HOW THE (byte) CAST IS *REQUIRED* TO GET THE RIGHT RESULT!!!!

  Serial.println(b1);
  Serial.println(b2);
  Serial.println(RING_BUFFER_SIZE);
  Serial.println(diff);
  Serial.println(diff2);
}

void loop()
{      
}

产生预期的:

12
5
64
249
57 //<--correct answer

而没有此处所示的“(byte)”转换:

void setup()
{
  Serial.begin(115200);
  byte b1 = 12;
  byte b2 = 5;
  const byte RING_BUFFER_SIZE = 64;

  byte diff = b2 - b1;
  byte diff2 = (b2 - b1) % RING_BUFFER_SIZE; //<---(byte) cast removed

  Serial.println(b1);
  Serial.println(b2);
  Serial.println(RING_BUFFER_SIZE);
  Serial.println(diff);
  Serial.println(diff2);
}

void loop()
{      
}

它产生:

12
5
64
249
249 //<--wrong answer

为什么不同?为什么模运算符仅适用于显式转换?

注:"byte" = "uint8_t"

5 - 12 给出 -7int)。所以你的代码确实 -7 % 64

从数学上讲,我们预计这会给出 57。但是,在 C 和 C++ 中,% 对于负数,它不会按照数学上的预期执行。相反,它满足以下等式:

(a/b) * b + a%b == a

现在,(-7)/64 给出了 0,因为 C 和 C++ 使用向零截断来进行正负整数除法。因此 -7 % 64 的计算结果为 -7.

最后,将 -7 转换为 uint8_t 得到 249

当您写 (byte)-7 % 64 时,您实际上是在 249 % 64 给出预期的答案。


关于 b2 - b1 的行为:所有整数运算至少以 int 精度进行;对于 - 的每个操作数,如果它是比 int 更窄的整数类型,它首先被提升为 int(保持值不变)。如果在此升级后类型不同(在这种情况下它们不会),则可能会发生进一步的转换。

在代码中,b2 - b1 表示 (int)b2 - (int)b1 产生 int;无法指定以较低精度执行操作。

算术运算要对 int 或更大的数进行运算。所以,你的 byte 在被减去之前被提升为整数 - 而且,你可能会得到实际的 int,C/C++ 是可以的,因为它们可以容纳 byte.

的整个范围

如果减法的结果向下转换为 byte,它会给出预期的溢出行为。但是,如果您在 diff2 计算中省略了转换,那么您将对 negative int 进行取模。并且,因为 C/C++ 带符号的除法向零舍入,带符号的模数与被除数的符号相同。

这里的第一个失误是期望减法直接作用于您的 byte 类型,或者将您的无符号 byte 转换为无符号 int。级联问题是忽略了 C++ 有符号除法的行为(如果您不知道应该首先期望有符号算术成为一个问题,这是可以理解的)。

请注意,如果您的 RING_BUFFER_SIZE 不是 2 的幂,那么对于这种情况,除法将无法正常工作。并且,由于它是二的幂,请注意:

(b2 - b1)&(RING_BUFFER_SIZE-1)

应该可以正常工作。

最后(如评论中所建议的),进行环形缓冲区减法的正确方法是确保 b1 < RING_BUFFER_SIZE (这对环形缓冲区操作有意义),并使用类似:

(b2>b1)? b2 - b1 : RING_BUFFER_SIZE + b2 - b1