计算机的运算方法

计算机的运算方法

C语言中的基本数据类型有:

  • 整型(short、int、long、char)
  • 浮点型(float、double)
  • 指针

考虑到指针的本质是无符号整型,于是归根结底来说就两个类型:整型和浮点型

  1. 整型

    存储:

    这个环节以4bit的有符号整型为例

    现今计算机中的int几乎全部以补码形式存储,是因为补码自有他的精妙所在,为了阐述这种精妙,需要引入原码,反码的概念,但这两个东西是已经被淘汰掉的存储形式,因此没必要记忆它们,目前来看,原码补码主要两个用处:一是做卷子用,二是辅助理解补码。

在这里插入图片描述

上面是4bit的有符号整型【-8~7】的原码反码和补码。

首先简单粗暴地给出三者的变换规则(考试用):

由于众所周知的原因,这里只有负数的变换

  • 【原码】->【反码】:符号位不动,数值部分按位取反
  • 【反码】->【补码】:符号位不动,数值部分末位加一
  • 【原码】->【补码】:符号位不动,数值部分取反再加一,但是这里还有一个超近道的方法,就是原码符号位依旧不动,数值部分从右往左遇到第一个1,这个1及其右边的位保持不动,左边全部取反。
发展沿革:

这个环节以8bit的有符号整型为例

关于具体的三种编码的出处没有考究到,但应该是按原码、反码、补码的顺序发展的。

背后的逻辑(并没按这个逻辑讲)

所有编码系统的设计,都在追求连续性和唯一性。
原码,反码和补码的演化,就在不断提高整数编码的这两方面性能。

原码:

那么随着计算机的发展,人们发现要去处理一些负数的计算,怎么办,就拿着计算机的第一位做符号位呗!在原码的规则下编码映射是这样的:

在这里插入图片描述

原码的优点:

  • 计算机可以表示负数了!
  • 从人的视角看编码代表的真值很方便,但是这样的一种映射符号位和数值部分是隔离的,

原码的缺点:

  • 计算机运算很不方便!这样的编码下符号位和数值部分是隔离的,想象一下5+(-3),计算机先要看二者是异号,然后看二者绝对值,谁大结果采取谁的符号,最后做运算。这一个加法的硬件设计将会很麻烦。

    能不能让符号位也像普通数值位一样一起参与运算?

  • 有如图所示的有两个编码都映射到0

  • 一些临界点运算会出错(后面将展示)

反码:

为了给底层的加法运算简化硬件设计,有了补码,其映射关系是这样的:

在这里插入图片描述

反码怎么来的,就是原码的数值部分按位取反,

反码的优点:

  • 解决了最重要的加法硬件设计复杂(或者依赖减法器)的问题,现在的加法器只要无脑相加就能得到正确的结果,不用再单独考虑符号位了。

反码的缺点:

  • 但是!没有解决真值0的重复映射问题
  • 在一些临界点运算还会出错(后面将展示)

补码

结局是补码解决了后两个问题:

在这里插入图片描述

从此加法器只要开足马力求和就好了,编码的精妙使得硬件很容易实现。但是即便是补码也还是会有溢出问题,但是!溢出是程序员的错,不是CPU的错。

前面三种方法在处理临界点数据的表现:

红色代表错了,蓝色正确

在这里插入图片描述

补码的精妙:

仍以8bit有符号整型为例

历史为什么会选择补码?当然是它解决了以上的种种问题,现在问题是为何它就能解决呢?

  1. 第一:0的重复映射问题

    在补码的编码下,原来的0x80被映射给了-128,这样2^8个编码有各自的映射对象。

  2. 第二:为什么编码加减结果正好可以代表对应数的计算结果

    补码编码精髓在于对负数的处理,负数的补码被定义成2^n+x

    这样对于任意的a,c,(a>0,c>0)考虑a-c

    1. a>c结果肯定是正数 a-c=a+(2^n-c) mod 2^n=a-c 没问题
    2. a<c结果肯定是负数 a-c=a+(2^n-c) mod 2n=2n+(a-c) 没问题,结果正好就是a-c的补码
  3. 第三:为什么反码不能解决这两个问题

    负数的反码是对x<0,其补码为2^n-1+x

    同样对于任意的a,c,(a>0,c>0)考虑a-c

    1. a>c结果肯定是正数 a-c=a+(2^n-1-c) mod 2^n=a-c -1 这显然不行
    2. a<c结果肯定是负数 a-c=a+(2^n-1-c) mod 2n=2n-1+(a-c) 没问题,这种情况下是正确的
    3. 当然容易知道a=c时也是正确的

补充:

余数定理:

如果a ≡ b (mod m),c ≡ d (mod m) 那么:

(1)a ± c ≡ b ± d (mod m)

(2)a * c ≡ b * d (mod m)

补码的映射关系如图:

在这里插入图片描述

小结:int的情况只不过时数量上的扩充,至于无符号数就是跟钟表完全一样的循环转圈

  1. 浮点型

    以单精度浮点数为例

IEEE754格式

日常开发中不可能只用整型,肯定有使用小数的场景,给你一个32bit的位置,可以用差不多40亿个不同的数编码,那么如何映射这些编码映射的范围更广呢?答案是采用浮点数格式,也即

在这里插入图片描述

这就是现行IEEE754标准进行对单精度浮点数的定义:

  • 一个符号位是刚需
  • 8是指数的存储空间,这个决定了浮点数上下所能达到的范围
  • 23是尾数(有效数)的空间,这个决定了浮点数的精度
  • 8和23是精度和范围权衡的结果

这样的一个32位编码映射到的小数是这样的:

在这里插入图片描述

补充概念:

移码:对 X 加上一个常数 2^(n-1),也就是上面的Bias,把 X 本身转换为一个正数,再以正数编码。

练习:

  1. 用IEEE754单精度表示-0.75(10进制)

    1. 化为二进制小数:-0.11

    2. 用规格化科学记数:-1.1X2^(-1)

    3. 对比上面的式子写出三个部分的值

      1. S=1
      2. Fraction=0.10000000000000000000000(小数点后23位)
      3. Exponent=-1+bias=-1+127=126=01111110(二进制)
    4. 一个萝卜一个坑

在这里插入图片描述

  1. 二进制转十进制浮点数

    0xc0a00000

    1. 写成二进制并按格式拆分:1 10000001 0100000000000000000000
    2. 写出三部分的值
      1. S=1
      2. Fraction=0.25
      3. Exponent=129
    3. 计算十进制浮点数
    4. -1.25*2^2=-5

为什么偏移是127?为什么要把指数偏移一下?

浮点数运算

练习:

0.5和-0.4375相加(使用4位精度)

先将二者化为规格化科学记数法:0.5=1.000x2^-1 -0.4375=-1.110x2^-2

  1. 将最小指数的数的有效位进行右移,直到其指数和较大数匹配:-1.110x2^-2=-0.111x2^-1
  2. 将有效数相加-0.111x2^-1 + 1.000x2^-1 = 0.001x2^-1
  3. 将和规格化并检查上溢和下溢0.001x2^-1 = 1.000x2^-4
  4. 舍入和 这里不需要舍入1.000x2^-4 =0.0625(10进制)

0.5和-0.4375相乘(使用4位精度)

  1. 将不带偏阶的指数相加 (-1)+(-2)= (-3)
  2. 有效数相乘:1.000 *1.110=1.110x2^-3
  3. 检查有效数的积是否规格化,这里是的;检查指数是否上溢和下溢,这里127>-3>-126,没问题
  4. 对积舍入
  5. 定符号 结果-0.21875
总结:
  1. int的范围是-2^31~2^31-1,两个特殊点是0xffffffff=-1,0x80000000=-2^31
  2. float的可表示范围是

参考资料:

【1】原码、反码、补码的产生、应用以及优缺点有哪些? - 达达手写的回答 - 知乎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值