30 – 136 = 150

来源:互联网 发布:c语言面试题 编辑:程序博客网 时间:2024/05/29 17:07

之前在网上碰到这样一个问题:

char a = 30, b = 136;printf("a – b = %d\n", a - b);

答案是150。在机器上证实确实是这个结果,可是不明白计算机是如何计算的,于是在网上查阅相关资料明确了以下几点:

1、无论是什么数值,也无论这个数值是用什么进制表示的,计算机内存中存储的都是这个数值二进制形式的补码

2、对于一个有符号数的符号位,0表示该数是一个正数,1表示该数是一个负数。对于不同数据类型的数,该种数据类型的最高比特作为该数的符号位。例如char类型的bit7、int类型的bit31、long long类型的bit63(其他类型以此类推)均作为相应数据类型的数的符号位。

3、一个数转化为补码的过程为:将一个数按其数据类型展开为二进制形式,该数据类型的符号位对于正数加0,对于负数加1。其它的位对于正数无需改变,对于负数将所有位按位取反后末位加1。

4、关于数值类型的转换:将短类型转换为长类型时,该短类型符号位的比特值填充扩展的比特。将长类型转换为短类型时,根据短类型的长度截断长类型。

5、减去1个补码,则这个补码需要所有位按位取反后末位加1,然后计算与另一个补码的加法。

6、补码的加减法均是扩展到32位或64位进行运算(只有long long类型扩展到64位,其他类型均扩展到32位进行运算)。

 

接下来看几个例子:

例1

#include <stdio.h> int main(void){        long long a = 0x8000000180000001, b = -0x8000000180000001;         printf("a = 0x%0llX\tb = 0x%0llX\n", a, b);         return 0;}

输出为:a = 0x8000000180000001  b =0x7FFFFFFE7FFFFFFF

简要分析一下这个例子。

a是一个long long类型的正数(虽然这个正数已经溢出,但是因为没有使用负号,所以计算机就认为它是一个正数,并按正数转换为补码存入内存),首先将其按二进制形式展开,

bit63     bit62 ~ bit33     bit32    bit31     bit30 ~ bit1     bit0

   1                 0                1           1               0              1

bit63是该种数据类型的符号位,那么对于正数该比特加0,其他位无需改变。最后得到的结果为0x8000000180000001。

b是一个long long类型的负数(计算机看的不是符号位是几,仅仅是因为使用了负号),首先将其按二进制形式展开(与上图相同),bit63是该种数据类型的符号位,那么对于负数该比特加1(加1后该比特变为0,进位溢出long long数据类型长度,则舍弃溢出位),其他位按位取反后末位加1,会变为如下形式,

bit63     bit62 ~ bit33     bit32    bit31     bit30 ~ bit1     bit0

   0                 1                0           0               1              1

最后得到的结果为0x7FFFFFFE7FFFFFFF。

 

例2

#include <stdio.h> int main(void){        char c = 0x180, d = -0x181;         printf("c = 0x%08X\td =0x%08X\n", (int)c, (int)d);         return 0;}

输出为:c = 0xFFFFFF80  d = 0x0000007F

简要分析一下这个例子。

c是一个char类型的正数,但明显赋的值已经溢出了,所以数值需要截断。char类型占1Byte,所以取后8bits。按照正数的补码转换规则,最后存入内存中的c为0x80。此时观察c的符号位为1,那么将其转换为int类型时,扩充出来的bit都需要赋值为1,那么最后以int类型打印c的结果为0xFFFFFF80。

d是一个char类型的负数,但明显赋的值依旧溢出了,所以数值需要截断。按照负数的补码转换规则,最后存入内存中的d为0x7F。此时观察d的符号位为0,那么将其转换为int类型时,扩充出来的bit都需要赋值为0,最后以int类型打印d的结果为0x0000007F。

 

最后回到标题的例子,一切就都很容易分析了。

例3

#include <stdio.h> int main(void){        char a = 30, b = 136;         printf("Variable\tHexadecimal\tDecimal\n");        printf("------------------------------------------\n");        printf("a\t\t0x%08X\t%d\n", a, a);        printf("b\t\t0x%08X\t%d\n", b, b);        printf("a - b\t\t0x%08X\t%d\n", a - b, a - b);        printf("b - a\t\t0x%08X\t%d\n", b - a, b - a);         return 0;}


输出为:

Variable        Hexadecimal     Decimal

------------------------------------------

a                     0x0000001E         30

b                     0xFFFFFF88        -120

a - b                0x00000096         150

b - a                0xFFFFFF6A        -150

简要分析一下这个例子。

a和b均是char类型的正数,那么a和b存入内存分别为0x1E和0x88。扩展到32位进行运算,则a和b分别为0x0000001E和0xFFFFFF88。由于要减去b,所以b应将所有位按位取反后末位加1,那么b变为0x00000078。此时与a相加,得到0x00000096。

此处扩展b - a,其实同理。将a所有位按位取反后末位加1,得到a为0xFFFFFFE2。此时与b相加,得到0xFFFFFF6A。

 



看完上面这些例子,会不会感到整个人都不好了?难道计算机连这么简单的减法都计算不了了?

在此要说明,以上的例子都是为了讨论问题而设置的,在实际的编程过程中最好不要使用。有没有注意到char类型的变量范围是-128~127,而上面例子中char b = 136已经越界了?有没有注意到char b本来存的是136,怎么到了内存中变成-120了?没错,你给了一个变量超出它能力范围的数值,这个数值在其能力范围内实际对应一个负数,你对这样的数值做加减法怎能得到正确的结果?

所以在实际编程中最好使用int类型存储数值(一般也都是这么做的),将上面最后一个例子的变量a和b的类型均改为int,结果自然都是正常的。


但是最后还是有个疑问,难道char类型的就真的用不了了吗?就算我越界了,但我就是想用,能!不!能!用!?

额,答案当然是肯定的。使用一个char类型的变量存储结果,然后再打印就能得到正确的值。原理就在于文章开始处总结的第4点,将短类型转换为长类型时会用符号为填充其扩充的比特位,

#include <stdio.h>int main(void){    char a = 30, b = 136, c = 0;    c = a - b;    printf("Variable\tHexadecimal\tDecimal\n");    printf("------------------------------------------\n");    printf("a\t\t0x%08X\t%d\n", a, a);    printf("b\t\t0x%08X\t%d\n", b, b);    printf("a - b\t\t0x%08X\t%d\n", a - b, a - b);    printf("c\t\t0x%08X\t%d\n", c, c);    return 0;}


输出为:

Variable        Hexadecimal     Decimal

------------------------------------------

a                     0x0000001E         30

b                     0xFFFFFF88        -120

a - b                0x00000096         150

c                     0xFFFFFF96       -106


原创粉丝点击