CPP无符号整型减法溢出

阅读一段环状序列数的次序比较代码

/// This assumes no two active sequence values will be more than
/// (1 << (sizeof(unsigned int) * 8 - 2)) - 1 different from each other.
int difference(unsigned int a, unsigned int b)
{
    return static_cast<int>(a - b);
}

这里为什么要假定两个序号之间距离不会超过unsigned int所能表示的最大序数1/4?
因为注释写错了!!!

CPU减法都由ALU(Arithmetic and Logic Unit)负责运算,ALU不知道也不关心两个输入二进制数所代表的含义,它只会对这两个数做加法(二进制逐位相加).

所以对于两个数相减, cpu运算的其实是元素1加上元素2的相反数

为了统一运算逻辑并遵循逻辑电路尽量简单的原则
现代计算机内负整型的表示方法一般为补码,以下用5bit长度举例
首bit标识正负,可标识十进制范围为 +15~-16

-13的二进制补码表示法:
1. 设A为+13, 二进制表示为 01101
2. A的反码(二进制按位取反)为 10010
3. 反码加一 10011 即为 -13的二进制补码
ALU加法验证:
   01101
   10011
(1)00000      

另例如:
+13二进制   01101
-10的补码   10110
+13-10=+3  00011
(溢出位被丢弃)
或:
+13二进制   01101
-14的补码   10010
+13-14=-1  11111
做取反逆运算 减1->11110 取反->00001 =>十进制+1则此补码标识十进制-1  

按上述规则分解运算如下

void main()
{

    uint8_t a = 246; //0xf6

    uint8_t b = 2; //0x02

    //将b段内存解读为带符号8bit整型,二进制首位将被解读为符号位,余下7位表示数值
    int8_t c = static_cast(b); //2 | 0x02

    //取补码
    int8_t neg_b = -b; //-2 | 0xfe

    //244距离8bit最大值256还差12个值, 所以为-12
    int8_t diff = a + neg_b; // -12 | 0xf4
    //首位做数值解读时为244
    uint8_t udiff = static_cast(diff); //244 | 0xf4



    /*
以8bit为例:
在不考虑位溢出的情况下, a-b得到的值即为普通减法运算的结果(例如将结果存储至16bit有符号整型中)
而当结果存储至8bit有符号整型且 |a - b| > 127 时,由于位溢出,
减法得到的结果会由于位信息解读方法而变化

例如:
1. |a - b| > 127, a < b
如: {a=10, b=196}
    a:       0000 1010
    b的补码:  0011 1100
             0100 0110
首位解读为符号位, 得到十进制值 70

2. |a - b| > 127, a > b
如: {a=228, b=100}
    a:       1110 0100
    b的补码:  1001 1100
             1000 0000
首位解读为符号位, 得到十进制值 -128

其实这里做了两步运算
1.先按二进制减法得到无符号的减法结果(结果因为带符号可能超出8bit,超出位被抛弃,但不会影响8bit内的二进制值)
2.按带符号整型的解释法转换出10进制数

8bit无符号整型减法必定落在[-255, 255],即其绝对值能用8bit表示
其中[-128, -1],[0, 127]能被8bit带符号整型正常解释
而[128, 255]将被映射到[-128, -1]
[-255, -129]将被映射到[1, 127]

128:
1000 0000 => -128
255:
1111 1111 => -1

-255
(1) 0000 0001 => 1
-129
(1) 0111 1111 => 127


所以10进制上如何解释,不会影响二进制的值,二进制值本质上表示的还是无符号的序数

*/

    getchar();
};

总结:
在编写代码时需重视编译器对于类型转换的warning, 防止未关注的位溢出带来与预期不一致的运算结果.


补码设计原理

答案就是:模。

“模”是指一个计量系统的计数范围。例如:

时钟:计量范围0~11,存在 模=12

计算机:n位的计算机计量范围0~2^(n)-1,即 模=2^n

“模”在计量器上表现不出来,计量器上只能表示出模的余数。

任何有模的计量器,均可化减法为加法运算

例如:时钟由10点调至6点,有两种方法:

顺拨8小时:10+8 = 12+6 = 6

倒拨4小时:10-4 = 6

于是我们可以看出,在模为12的系统中,加8和减4效果是一样的。因此,减法可以用加法代替。

上例中,对于“模”而言,8和4互为“补数”。所谓补数,实际上模拟了数学中“补角”的概念,同样的,如果在某个计量系统中,两数之和刚好等于模,则称它们互为补数。

对于计算机,原理是一样的。假设n位计算机(n=8),所能表示的最大数是11111111,若再加1为100000000(9位),因只有8位,最高位1舍弃。又回到00000000,所以8位二进制系统模为2^8。所以在这样的系统中,减法问题也可以化成加法问题,只需把减数用相应的补数表示就可以了。这便是为什么计算机引入补码的原理。

于是恍然大悟,计算机使用补码存储的好处了

X - Y <=> X + [-Y]补

十进制:x-6 <=> x+250 (在模256系统中,250与6互为补数,减6相当于加250)

用二进制表示便是:x + 11111010 (11111010为-6的补码,即250的原码)

引用自 http://www.ahathinking.com/archives/75.html

你可能感兴趣的:(cpp)