计算机中整数的表示和整数运算

本文详细阐述了计算机中整数的二进制表示方法及其四则运算原理,特别是加法和减法如何通过补码运算统一处理,以及不同数据类型在进行运算时的转换过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 计算机中整数的表示

2. 计算机中整数的四则运算


1. 计算机中整数的表示

计算机只认识0和1,因此任何数都需要表示成二进制的形式。计算机系统规定,最高位用于表示整数的符号位,0表示正数,1表示负数,这是为了方便高级语言标识有符号数和无符号数。在处理器指令层面,指令在计算时不区分整数是否是有符号,还是无符号,统一按有符号数处理,至于应当明符号,还是无符号,由程序员去决定(你认为这是有符号数,就当成有符号数去解析,你认为是无符号数,就当成无符号数去解析)。

数的运算包括加减乘除,人类首先发明的是加法器,因此,加法问题最为简单,首先得到解决;乘法问题可以看成是数的累加,即移位,逻辑判断,累加结合,则解决了加法问题,乘法问题很容易解决。除法问题可以看成是减法的移位,逻辑判断,累减,所以核问题是解决整数的减法问题。因为减去一个数,可以看成是加上一个我负数,因此,核心问题就成了如何表示这个负数。

如果一个正数,和一个与它绝对值相同的负数,这两个数各自在计算器中的表示,通过逻辑电路的加法运算法则,恰好产生了溢出,从而其有效位全部是0,则这两个数互为对方的某种形式,则减法问题得到解决。

为了便于描述,我们先提出原码:某个整数(0,正整数,负整数),其绝对值的二进制表示形式。注意,原码是描述补码的基础,计算机中的整数不是用原码表示,为了统一,我们说计算机中的数是用补码表示。

 因为找到一个数与其相反数相加为0,直接操作并不容易,要产生进位,计算电路的设计更为复杂,而且没有办法通用一套电路。于是,找到一个数与其相加等于1(如果是一个字节,则为11111111,我们以这个为例),再在这个数基础上加1,产生溢出,则结果就为0了。而求得相加等于1的值,很容易,在原码的基础上按位取反即可,即反码

取得了反码,在这个基础上加1,正好产生溢出为0,这就满足了我们转换减法为加法的目的,在电路实现上,用一套电路即可,计算时,统统当成有符号数进行运算。以-1为例,我们考虑一个字节的运算:

原码形式:00000001

反码形式(按位取反):11111110 (与原码相加正好为11111111)

补码形式(补码加1): 11111110 + 00000001 = 11111111(0xFF)

因为我们以正整数为基础进行的补码运算,而为了统一称乎,我们称计算机中的整数为用补码表示。因此,正整数的补码就是其本身,正数不需要转换,只需要转换负数。而负数只需要按照这种规则进行转码,就能与其对应的正整数通过电路的逻辑相加运算结果为0,完美地解决了转减法为加法的问题,并统一使用一套电路转换编码的时候不考虑符号,因为负数补码的概念已经将符号编码在其中了,因为它与对应的正数相加恰好为0。比如,我们计算1-3=1+(-3):

1的补码表示:00000001

-3的补码形式:求原码00000011,求反码11111100,求补码11111101

1-3=1+(-3) = 00000001+11111101=11111110(-2)

因此,计算机中的计算结果值就是11111110,对于这个结果值,你要把它解析成正数还是负数,那就是程序员的责任了,比如,在使用C++编程时,你将它转换成有符号数int,因为最高位是负号,意味着这是负数,这个编码值应当看成是负数的补码表示,在显示时,应当转换为对应的原码值加上符号,这个工作由C++编译器替程序员完成了,这时候我们在调试时显示的值是-2,但是我们显示时内存值是11111110。注意,计算机加减运算时不区分有符号还是无符号,最高位也是当成整值参与计算

2. 计算机中整数的四则运算

当我们在编程进行四则运算的时候,我们应当遵循数据类型一致的原则,因为你用一个int变量与一个unsigned int变量相加,计算机也会给你内部转换成一致类型再进行运算,而且写出这样的表示也没有意义。下面举几个例子,看看编译器是如何隐式处理的。以VS2022编译环境为例,编写X64代码。

示例1:1字节有符号数加法运算

void Test()

{

    signed char a = 1;

    signed char b = -3;

    signed char total = (signed char)(a + b);  

}

查看反汇编译代码:

void Test()

{

00007FF6FCC12220  sub         rsp,18h 

    signed char a = 1;

00007FF6FCC12224  mov         byte ptr [rsp],1 

    signed char b = -3;

00007FF6FCC12228  mov         byte ptr [b],0FDh 

    signed char total = (signed char)(a + b);

00007FF6FCC1222D  movsx       eax,byte ptr [rsp] 

00007FF6FCC12231  movsx       ecx,byte ptr [b] 

00007FF6FCC12236  add         eax,ecx 

00007FF6FCC12238  mov         byte ptr [total],al 

   

}

00007FF6FCC12228  mov         byte ptr [b],0FDh 

这一句,-3被转换成了补码形式0FDh(前缀0,后缀h表示数的十六进制表示)

00007FF6FCC1222D  movsx       eax,byte ptr [rsp] 

00007FF6FCC12231  movsx       ecx,byte ptr [b]

这两句,编译器将以字节表示的数符号扩展到32位,这是编译器作的优化,这样计算效率高。

00007FF6FCC12236  add         eax,ecx

这是汇编语言加法指令,运算时按逻辑加运算,不管什么符号位。

00007FF6FCC12238  mov         byte ptr [total],al 

这一句是我们使用的强制转换方法。

这个例子可以看出两点编译器在计算字节数据类型时,会将其转换成32位的整数再进行运算;运算指令不管符号位,把符号位也当成正常数据参与运算,符号只体现在编码中,负数已经表示成了补码形式,而高级语言在声明有符号无符号时,实际上是告诉编译器,应当将数据按何种形式进行编码

示例2:1字节有符号数和4字节有符号数加法运算

void Test()

{

    signed char a = 1;

    int b = -3;

    auto total = a + b;

}

查看反汇编译代码:

void Test()

{

00007FF7DC942220  sub         rsp,18h 

    signed char a = 1;

00007FF7DC942224  mov         byte ptr [rsp],1 

    int b = -3;

00007FF7DC942228  mov         dword ptr [b],0FFFFFFFDh 

    auto total = a + b;

00007FF7DC942230  movsx       eax,byte ptr [rsp] 

00007FF7DC942234  add         eax,dword ptr [b] 

00007FF7DC942238  mov         dword ptr [total],eax 

}

00007FF7DC942230  movsx       eax,byte ptr [rsp]

编译器将以字节表示的数符号扩展到32位。

这个例子可以看出一点两个类型大小不一的整数相加,编译器会先将字节小的数作符号扩展成与类型大的数的类型一致,再参与运算

示例3:有符号数与无符号数混全加法运算

void Test()

{

    unsigned int a = 1;

    int b = -3;

    auto total = a + b;

}

查看反汇编译代码:

void Test()

{

00007FF62B5F2220  sub         rsp,18h 

    unsigned int a = 1;

00007FF62B5F2224  mov         dword ptr [a],1 

    int b = -3;

00007FF62B5F222C  mov         dword ptr [rsp],0FFFFFFFDh 

    auto total = a + b;

00007FF62B5F2233  mov         eax,dword ptr [rsp] 

00007FF62B5F2236  mov         ecx,dword ptr [a] 

00007FF62B5F223A  add         ecx,eax 

00007FF62B5F223C  mov         eax,ecx 

00007FF62B5F223E  mov         dword ptr [total],eax 

}

这个例子可以看出一点编译器不管啥有符号无符号,你标识为有符号,如果输入的是负值,会把它转换为负数的被码形式,再参与运算,至于计算结果正确与否,那是程序员去判断的事。不同的编译器可能对计算值处理不一样,VS2022会将其默认为和为unsigned int型,如果值为负数,则明显结果错误,因此,何必要写这种代码去考验编译器呢?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值