翻转字节,进行算术运算并再次将它们翻转回来

Flipping bytes, doing arithmetic and flipping them back again

我有一个 programming/math 相关问题,涉及大端和小端之间的转换以及计算。

假设我们有两个小端模式的整数:

int a = 5;
int b = 6;
//a+b = 11

让我们翻转字节并再次添加它们:

int a = 1280;
int b = 1536;
//a+b = 2816

现在,如果我们翻转 2816 的字节顺序,我们将得到 11。所以本质上我们可以在小端和大端之间进行算术计算,并且一旦转换它们就代表相同的数字?

这背后有计算机科学界的theory/name吗?

这似乎只起作用,因为您碰巧选择了足够小的数字,以至于它们以及它们的总和适合一个字节。只要您的数字中发生的所有事情都在其各自的字节内,您显然可以根据需要对字节进行洗牌和取消洗牌,这不会有什么不同。如果您选择更大的数字,例如 1234 和 4321,您会注意到它不再有效。事实上,您很可能最终会调用未定义的行为,因为您的 int 会溢出……

除此之外,您几乎肯定会想阅读以下内容:https://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html

如果加法涉及进位则不起作用,因为进位是从右到左传播的。交换数字并不意味着携带开关方向,因此溢出到下一个字节的任何字节都会不同。

让我们看一个十六进制的例子,假设字节顺序意味着交换每个 4 位半字节:

int a = 0x68;
int b = 0x0B;
//a+b:  0x73

int a = 0x86;
int b = 0xB0;
//a+b: 0x136

816 + B16 是 1316。该 1 被携带并添加到第一个总和中的 6。但是在第二个和中没有右进位加到6上,左进位溢出到第三个十六进制数。

首先需要注意的是,你假设int在C中有16位错误的。在大多数现代系统中 int 是 32 位类型,所以如果我们 reverse (不是翻转,这通常意味着取补码)我们将得到 5 的字节 83886080 (0x05000000),不是 1280 (0x0500)

  • Is the size of C "int" 2 bytes or 4 bytes?
  • What does the C++ standard state the size of int, long type to be?

还要注意,你应该用十六进制写,这样更容易理解,因为计算机不以十进制工作:

int16_t a = 0x0005;
int16_t b = 0x0006;
// a+b = 0x000B

int16_t a = 0x0500; // 1280
int16_t b = 0x0600; // 1536
//a+b = 0x0B00

好吧,正如其他人所说,ntohl(htonl(5) + htonl(6)) 恰好与 5 + 6 相同 只是因为你的数字很小,而他们的反向总和却没有溢出。选择更大的数字,您会立即看到差异


但是 属性 确实适用于 ones' complement 对于值存储在 2 个较小部分的系统,例如这种情况

在补码中,通过将进位传播回进位,与 end-around carry 进行算术运算。这使得补码算术 endian 独立 如果有由于“循环进位”,只有一个内部“进位中断”(即存储的值被分成两个单独的块)

假设我们有 xxyy 和 zztt 那么 xxyy + zztt 就是这样完成的

            carry
        xx        yy
      + zz <───── tt
      ──────────────
  carry aa        bb
     │             ↑
     └─────────────┘

当我们反转块时,yyxx + ttzz 的携带方式相同。因为 xx、yy、zz、tt 是任意长度的位块,它适用于 PDP 的混合字节序,或者当您将 32 位数字存储在两个 16 位部分中时,将 64 位数字存储在两个 32 位部分中...

例如:

  • 0x7896 + 0x6987 = 0xE21D
    • 0x9678 + 0x8769 = 0x11DE1 → 0x1DE1 + 1 = 0x1DE2
  • 0x2345 + 0x9ABC = 0xBE01
    • 0x4523 + 0xBC9A = 0x101BD → 0x01BD + 1 = 0x01BE
  • 0xABCD + 0xBCDE = 0x168AB → 0x68AB + 1 = 0x68AC
    • 0xCDAB + 0xDEBC = 0x1AC67 → 0xAC67 + 1 = 0xAC68

或者 John Kugelman 上面的例子:0x68 + 0x0B = 0x73; 0x86 + 0xB0 = 0x136 → 0x36 + 1 = 0x37

循环进位是选择补码作为TCP校验和的原因之一,因为你可以很容易地计算出更高精度的和。 16 位 CPU 可以正常工作在 16 位单元中,但是 32 位和 64 位 CPU 可以并行添加 32 位和 64 位块,而不用担心 SIMD 不可用时的进位,例如 SWAR technique