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


    目录

    1. 计算机中整数的表示

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


    1. 计算机中整数的表示

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

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

    如果一个正数,和一个与它绝对值相同的负数,这两个数各自在计算器中的表示,通过逻辑电路的加法运算法则,恰好产生了溢出,从而其有效位全部是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型,如果值为负数,则明显结果错误,因此,何必要写这种代码去考验编译器呢?

  • 相关阅读:
    论文阅读 (71):Optimal Margin Distribution Machine for Multi-Instance Learning
    idea怎么将克隆的代码上传到自己的gitee(保姆级教程)
    【源码解析】分库分表框架 Shardingsphere 源码解析
    js arrys函数
    小白学习-ElasticSearch教程(3) -文档查询之term精确查询
    永磁材料测试仪系统全自动测量软件
    Git 学习(三)---- GitHub 远程库操作
    Java String转double
    SignalR WebSocket通讯机制
    创意无限,批量为视频添加个性化图片,让你的视频独一无二
  • 原文地址:https://blog.csdn.net/ComputerInBook/article/details/126335978