游乐游手机版
首页/AI热点日报/热点详情

计算机数值类型编码、运算与转换原理详解

类型:热点整理2026-07-11
计算机中整数算术运算可能因超出表示范围而产生溢出,例如正数相加得负数。无符号与有符号整数的加法、取反、乘法均有特定溢出规则:加法通过截断处理,取反对TMin_w特殊处理,乘法结果截断后保留低w位,左移可替代乘以2的幂以优化性能。

在计算机中,整数的算术运算往往不符合日常直觉。例如,两个正数 21474836471 相加,结果却是一个负数 -2147483648。这是因为运算结果超出了整数类型的表示范围,这种现象被称为 溢出 。本教程将系统介绍整数类型常见的算术运算,包括加法、取反、乘法、除法,并详细解释溢出时的处理方式,帮助您理解计算机底层的运算规则。

算术运算概述

先看一个典型的溢出例子(Java 语言):

// Java语言
public static void main(String[] args) {
    int i1 = 2147483647;
    int i2 = 1;
    System.out.printf("2147483647+1=%d\n", i1+i2);
}
// 输出结果:2147483647+1=-2147483648

正数相加得到负数,根本原因是 结果超出了 int 类型(32 位有符号)的表示范围,系统对结果进行了截断。下面我们逐个讲解各种运算的溢出规则。

整数加法

(1)无符号整数加法运算

对两个 w 位的无符号整数,有 0 ≤ x, y < 2^w,真实和为 x + y。如果用 w 位表示结果,当 x + y ≥ 2^w 时,运算产生溢出。系统会对结果进行截断,只保留低 w 位

例如 w = 4 的场景:

+^u_w 表示 w 位无符号整数加法,规则为:

  • 正常:x + y(当 x + y < 2^w
  • 溢出:x + y - 2^w(因为截断时舍去了最高位的 2^w

(2)有符号整数加法运算

对两个 w 位的有符号整数(补码表示),范围是 -2^(w-1) ~ 2^(w-1)-1。当真实结果 x + y 超出此范围时,就会产生溢出。有两种溢出情况:

  • 正溢出x + y ≥ 2^(w-1),截断后结果变为负数。
  • 负溢出x + y < -2^(w-1),截断后结果变为正数。

例如 w = 4 的场景:

注意:两个 w 位的负数相加,即使结果需要 w+1 位表示,也可能是正常场景。这取决于截断后 w 位的最高位(符号位)——若为 1 则结果正常(负),若为 0 则产生负溢出(正)。

规则如下:

  • 正溢出:真实值 x + y,实际补码结果为 x + y - 2^w
  • 负溢出:真实值 x + y,实际补码结果为 x + y + 2^w

为什么负溢出场景下,w 位一定是 0?

负溢出条件为 x + y < -2^(w-1),即 x + y + 2^w < 2^(w-1)。由于 x, y 均为负数,它们的 w 位二进制表示最高位都是 1,相加后第 w 位(进位)必然为 0,否则无法满足不等式。

小提示:无符号整数和有符号整数的加法运算本质一样,都可以拆解为 4 步

  1. 先计算出真实加法结果值。
  2. 对结果用 w+1 位二进制表示。
  3. 截断,保留低 w 位。
  4. 将截断后的 w 位二进制值转换回十进制(无符号用无符号编码,有符号用补码编码)。

整数取反(求相反数)

取反即求相反数,给定整数 x,它的相反数 -x 满足 x + (-x) = 0

(1)无符号整数的取反

对 w 位无符号整数 x

  • x = 0 时,-x = 0
  • x > 0 时,只有在溢出场景下才能得到 0,即 x + (-x) = 2^w,因此 -x = 2^w - x

规则:-^u_w = (2^w - x) mod 2^w,即 2^w - x(当 x ≠ 0)。

例如 w = 8:

// C++
int main() {
    uint8_t i1 = 0;
    uint8_t i2 = -i1;
    printf("i1=%u\n", i1);
    printf("-i1=%u\n", i2);

    uint8_t i3 = 100;
    uint8_t i4 = -i3;
    printf("i3=%u\n", i3);
    printf("-i3=%u\n", i4);
    printf("2^8-i3=256-%u=%u\n", i3, 256-i3);
    return 0;
}
// 输出结果
i1=0
-i1=0
i3=100
-i3=156
2^8-i3=256-100=156

(2)有符号整数的取反

对 w 位有符号整数 x

  • x ≠ TMin_w(最小负数 -2^(w-1))时,-x = -x(正常范围)。
  • x = TMin_w 时,-x = TMin_w(因为 2^(w-1) 不在范围内,溢出后自身等于自身)。

规则:-^s_w = -x(正常),-^s_w = TMin_w(当 x = TMin_w)。

例如 w = 8:

// C++
int main() {
    int8_t i1 = -128;
    int8_t i2 = -i1;
    printf("i1=%d\n", i1);
    printf("-i1=%d\n", i2);

    int8_t i3 = 100;
    int8_t i4 = -i3;
    printf("i3=%d\n", i3);
    printf("-i3=%d\n", i4);
    return 0;
}
// 输出结果
i1=-128
-i1=-128
i3=100
-i3=-100

整数乘法

基本乘法与溢出

乘法与加法类似:无符号和有符号整数的乘法运算也可以拆成“计算真实值→二进制表示→截断→转换”这 4 步。结果最多需要 2w 位表示。

无符号整数例子(w=8):

// C++
int main() {
    uint8_t i1 = 100;
    uint8_t i2 = 2;
    uint8_t i3 = i1 * i2;
    printf("normal: i1 * i2 = %d * %d = %d\n", i1, i2, i3);

    uint8_t i4 = 100;
    uint8_t i5 = 3;
    uint8_t i6 = i4 * i5;
    printf("overflow: i4 * i5 = %d * %d = %d\n", i4, i5, i6);
    return 0;
}
// 输出结果
normal: i1 * i2 = 100 * 2 = 200
overflow: i4 * i5 = 100 * 3 = 44

有符号整数例子(w=8):

// C++
int main() {
    int8_t i1 = -50;
    int8_t i2 = 2;
    int8_t i3 = i1 * i2;
    printf("normal: i1 * i2 = %d * %d = %d\n", i1, i2, i3);

    int8_t i4 = -128;
    int8_t i5 = 127;
    int8_t i6 = i4 * i5;
    printf("overflow: i4 * i5 = %d * %d = %d\n", i4, i5, i6);
    return 0;
}
// 输出结果
normal: i1 * i2 = -50 * 2 = -100
overflow: i4 * i5 = -128 * 127 = -128

移位优化乘法

大部分机器上,乘法运算消耗 3~10 个时钟周期,而加法或位运算仅需 1 个时钟周期。因此,可以用左移操作替代乘以 2 的幂

x 左移 k 位,相当于乘以 2^k(不考虑截断)。考虑截断时,左移 k 位等同于 x * 2^k mod 2^w(x * 2^k) mod 2^w(有符号按补码)。

例如 w=8 的无符号数:

// C++
int main() {
    uint8_t i1 = 100;
    uint8_t i2 = 4;
    uint8_t i3 = i1 * i2;
    printf("i1 * i2 = %d * %d = %d\n", i1, i2, i3);

    uint8_t k = 2;
    uint8_t i4 = i1 << k;
    printf("i1 << k = %d << %d = %d\n", i1, k, i4);
    return 0;
}
// 输出结果
i1 * i2 = 100 * 4 = 144
i1 << k = 100 << 2 = 144

有符号数例子:

// C++
int main() {
    int8_t i1 = -50;
    int8_t i2 = 4;
    int8_t i3 = i1 * i2;
    printf("i1 * i2 = %d * %d = %d\n", i1, i2, i3);

    int8_t k = 2;
    int8_t i4 = i1 << k;
    printf("i1 << k = %d << %d = %d\n", i1, k, i4);
    return 0;
}
// 输出结果
i1 * i2 = -50 * 4 = 56
i1 << k = -50 << 2 = 56

问题:如何将乘以任意常数 K 转换为移位操作?

将 K 分解为二进制形式,例如 14 = 1110_2,可以表示为 (x<<3)+(x<<2)+(x<<1)(x<<4)-(x<<1)。通用公式:若 K 的二进制中连续的 1 序列从高位 n 到低位 m,则 x * K = (x << (n+1)) - (x << m)

例如:

// C++
int main() {
    int8_t x = 5;
    int8_t K = 14;
    printf("x * 14 = %d\n", x*K);
    printf("(x<<3) + (x<<2) + (x<<1) = %d\n", (x<<3)+(x<<2)+(x<<1));
    printf("(x<<4) - (x<<1) = %d\n", (x<<4)-(x<<1));
    return 0;
}
// 输出结果
x * 14 = 70
(x<<3) + (x<<2) + (x<<1) = 70
(x<<4) - (x<<1) = 70

整数除法

除法运算比乘法更慢,通常需要 30 个时钟周期以上。除以 2 的幂可以用右移操作代替,但需要注意取整方式

  • 无符号整数:除以 2^k,右移 k 位相当于向下取整(floor)。
  • 有符号整数(补码):右移 k 位默认是算术右移(保留符号),对于负数,直接右移是向下取整(即向负无穷方向),而非向零取整。要实现向零取整,需要先加上偏置 (1< 再右移。

例子(w=8):

// C++
int main() {
    int8_t x = -50;
    int8_t K = 4;
    printf("x / 4 = %d\n", x/K);
    printf("x >> 2 = %d\n", x>>2);
    printf("(x+(1<<2))>>2 = %d\n", (x+(1<<2))>>2);

    uint8_t y = 50;
    uint8_t Z = 4;
    printf("y / 4 = %d\n", y/Z);
    printf("y >>> 2 = %d\n", y>>2);
    printf("(y+(1<<2))>>>2 = %d\n", (y+(1<<2))>>2);
    return 0;
}
// 输出结果
x / 4 = -12
x >> 2 = -13
(x+(1<<2))>>2 = -12
y / 4 = 12
y >>> 2 = 12
(y+(1<<2))>>>2 = 13

注意:有符号负数的直接右移会得到 -13(向下取整),而通过加偏置 (1< 再右移,可以得到向零取整的结果 -12 对于无符号数,加偏置反而会得到向上取整,因此不需要。

常见问题(FAQ)

  • 问:为什么两个正数相加会变成负数?
    答:这是有符号整数加法正溢出。真实结果超过了 2^(w-1)-1,截断后最高位(符号位)变为 1,因此解释为负数。
  • 问:所有负数相加都会溢出吗?
    答:不一定。两个 w 位负数相加,如果结果在 -2^(w-1)-1 范围内,则正常;只有当结果小于 -2^(w-1) 时才会发生负溢出。请看前文的判定规则。
  • 问:为什么取反操作中,最小负数取反等于自身?
    答:因为 -TMin = 2^(w-1),这已经超出了有符号整数的最大正值,按补码规则溢出后重新映射为 TMin
  • 问:用移位代替乘法时,需要考虑溢出吗?
    答:需要。移位操作本身也会产生截断,效果与乘法完全一致。例如 100 << 2 得到 144 而非 400,正是因为溢出。
  • 问:除法用右移替代时,无符号和有符号有什么区别?
    答:无符号右移是逻辑右移,自然向下取整;有符号右移是算术右移,对负数向下取整(向负无穷),若需向零取整,必须加上偏置((1<)后再右移。

总结:本教程详细讲解了整数四种算术运算(加法、取反、乘法、除法)的底层原理,特别强调了溢出时的截断规则。理解这些规则有助于编写健壮的代码,避免因溢出导致的逻辑错误,同时也让您掌握性能优化技巧(如移位代替乘除)。希望这份教程能帮助您扎实地掌握计算机算术运算的本质。

来源:https://m.elecfans.com/article/2077208.html

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。