算术运算整数的算术运算也不总符合我们的常识,比如下面这个例子:
// 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两个正数 2147483647 和 1 相加,实际运行结果却是一个负数 -2147483648。原因是,运算结果超出了 int 类型的表示范围,我们把这种现象称作 溢出 。
接下来,我们将介绍整数类型常见的几种算术运算,包括当运算出现溢出时,计算机系统的处理方式。
加法(1)无符号整数加法运算
对两个 w 位的无符号整数 ,有 ;如果仍用一个 w 位的整数表示相加结果,那么当结果在 范围时,也即结果需要 w + 1 位表示时,运算就产生溢出了。
如果出现溢出,系统会对结果进行截断,只保留低 w 位 。
比如,w = 4 场景下的一些例子:
因此,如果用 表示 w 位无符号整数的加法运算,那么它有如下规则:
其中,溢出场景因为对结果的进行截断,舍去了 位,所以结果需要减去 。
(2)有符号整数加法运算
对两个 w 位的有符号整数 ,有 ;如果仍用一个 w 位的整数表示相加结果,那么当结果在 和 范围时,运算就产生溢出了。
比如,w = 4 场景下的一些例子:
由上述例子可知, 两个 w 位的负数相加,即使结果需要 w + 1 位表示,也可能是正常场景。这取决于截断位 w 位后的最高位,为 1 则还属于正常场景,为 0 则属于溢出场景 。
因此,如果用 表示 w 位有符号整数的加法运算,那么它有如下规则:
正溢出场景下 ,原本 ,但按照补码编码方式,实际变成了 ,所以就有 。负溢出场景下 ,w 位一定是 0,原本 ,结果截断之后,实际变成了 ,所以就有 。为什么负溢出场景下,w 位一定是 0 ?
负溢出场景出现的条件是 ,也即 :
如果要使上述不等式成立, 必然为 0.
前面很多公式,但记住这个就行, 无符号整数和有符号整数的加法运算规则,本质都是一样的,可以拆解成 4 步 :
先计算出真实加法运算的结果值。对结果值用 w + 1 位二进制表示。再将 w + 1 位的二进制结果截断,保留低 w 位。将截断后的 w 位二进制值转换回十进制整数,得到最终结果。无符号整数用无符号编码,有符号整数用补码编码。取反取反,也即求相反数,给定一个整数 ,那么它的相反数 ,也即满足 。
(1)无符号整数的取反
对一个 w 位的无符号整数 ,对它取反,也即找到一个整数 ,使得 :
当 ,那么很容易得出 ;当 x > 0,只有在溢出场景才会存在 的可能。由前文可知,无符号整数加法溢出场景下, 时,有,。因此,如果用 表示 w 位无符号整数的取反运算,那么它有如下规则:
比如,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=0i3=100-i3=1562^8-i3=256-100=156(2)有符号整数的取反
对一个 w 位的有符号整数 ,对它取反,也即找到一个整数 ,使得 :
当 ,很容易得出 ,因为此时 ,仍是有效的范围。当 ,因为 已经不再有效范围内,所以只能是溢出场景。而 ,截断为 w 之后,刚好为 0。所以,此时 。因此,如果用 表示 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=-128i3=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 = 200overflow: i4 * i5 = 100 * 3 = 44
对于有符号整数 ,那么 ,即有符号整数的乘法运算结果最多也需要 2w 位来表示。
比如,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 = -100overflow: i4 * i5 = -128 * 127 = -128
大部分机器上,乘法运算消耗 3 ~ 10 个 cpu 时钟周期,而加法运算和位运算只消耗 1 个时钟周期,所以,追求极致性能的程序都会想办法通过加法运算和位运算来替代乘法运算。
如果不考虑截断,对 左移 k 位,会得到:
也即,对 左移 k 位 相当于乘以 。
如果考虑截断,就会存在溢出场景,对于 w 位的整数,左移 k 位效果也等同于 或 。
比如,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 = 144i1 << 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 = 56i1 << k = -50 << 2 = 56那么,对于与任意常数 k 的相乘,有没可能转换为移位操作 ?
任意常数 k,可以表示成 的形式,也即,由一系列连续的 0 和 连续的 1 组成,比如 14 可以表示成 。
假设只存在一个连续的 1 序列,从高到低, 位于 n 到 m 位,比如 14 中,n = 3,m = 1,那么:
比如, 就可以表示成 或者 :
// 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<> 2 = %d\\n, x>>2); printf((x+(12 = %d\\n, (x+(1<>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 = %d\\n, (y+(1<>2); return 0;}// 输出结果x / 4 = -12x >> 2 = -13(x+(1<>2 = -12y / 4 = 12y >>> 2 = 12(y+(1<>>2 = 13
AN5071各引脚功能电压资料
激光传感器工作原理_激光传感器应用
5G技术的兴起,ICT成为加速社会和经济可持续增长和发展的主要驱动力
绝缘轴承在电机中的应用,绝缘轴承原来这么强大
区块链在云存储的战争中具备什么优势
计算机系统对数值类型的编码、运算、转换原理介绍2
网络生态依靠政策完善多主体互动机制“网络”与“现实”的融合
小米6性能爆炸!骁龙835全面曝光 功耗逆天
SOS LAB将展示其差异化的混合LiDAR系统SL-1
关于后摩尔时代产业链的分析和介绍
什么时候才可以完成2G/3G的退网
MATLAB的Sobel图像边缘灰度值检测算法的详细公式和实现资料概述
浅析嵌入式存储系统设计方法
美国FCC开始着手统计有关使用华为与中兴设备的信息 美国偏远地区有望继续采用
华为在VR/AR领域发力 开发者应该如何把握机会
西方国家习惯于模拟电视而少用网络电视
服务器的分类
NVIDIA透露即将发布的Blender 3.0中将包括USD支持
SK 电讯采用了大量的是德科技解决方案,实现无缝切换和更高数据速率
短视频守正创新,推动网络视听持续健康发展