一、为什么负数要用补码表示?

以 int 类型的数字作为例⼦,int 类型是 32 位的,其中最⾼位是作为「符号标志位」,正数的符号位是 0 ,负数的符号位是 1 ,剩余的 31 位则表示⼆进制数据。

那么,对于 int 类型的数字 1 的⼆进制数表示如下:

⽽负数就⽐较特殊了点,负数在计算机中是以「补码」表示的,所谓的补码就是把正数的⼆进制全部取反再加 1,⽐如 -1 的⼆进制是把数字 1 的⼆进制取反后再加 1,如下图:

那么,为什么计算机要⽤补码的⽅式来表示负数?**在回答这个问题前,我们假设不⽤补码的⽅式来表示负数,⽽只是把最⾼位的符号标志位变为 1 表示负数,如下图过程:

如果采⽤这种⽅式来表示负数的⼆进制的话,试想⼀下 -2 + 1 的运算过程,如下图:

按道理, -2 + 1 = -1 ,但是上⾯的运算过程中得到结果却是 -3 ,所可以发现,这种负数的表示⽅式是不能⽤常规的加法来计算了,就需要特殊处理,要先判断数字是否为负数,如果是负数就要把加法操作变成减法操作才可以得到正确对结果。

解答:

如果负数不是使⽤补码的⽅式表示,则在做基本对加减法运算的时候,还需要多⼀步操作来判断是否为负数,如果为负数,还得把加法反转成减法,或者把减法反转成加法,这就⾮常不好了,毕竟加减法运算在计算机⾥是很常使⽤的,所以为了性能考虑,应该要尽量简化这个运算过程。

⽽⽤了补码的表示⽅式,对于负数的加减法操作,实际上是和正数加减法操作⼀样的。你可以看到下图,⽤补码表示的负数在运算 -2 + 1 过程的时候,其结果是正确的:

二、十进制小数与⼆进制的转换

  • 整数⼗进制转⼆进制:除2取整
  • ⼩数⼗进制转⼆进制:乘2取整

以 8.625 转⼆进制作为例:

最后把「整数部分 + ⼩数部分」结合在⼀起后,其结果就是 1000.101 。

但是,并不是所有小数都可以⽤⼆进制表示, 前⾯提到的 0.625 ⼩数是⼀个特例,刚好通过乘 2 取整法的⽅式完整的转换成⼆进制。

如果我们⽤相同的⽅式,来把 0.1 转换成⼆进制,过程如下:

可以发现, 0.1 的⼆进制表示是⽆限循环的。

由于计算机的资源是有限的,所以是没办法⽤⼆进制精确的表示 0.1,只能⽤「近似值」来表示,就是在有限的精度情况下,最⼤化接近 0.1 的⼆进制数,于是就会造成精度缺失的情况。

对于⼆进制⼩数转⼗进制时,需要注意⼀点,⼩数点后⾯的指数幂是负数

⽐如,⼆进制 0.1 转成⼗进制就是 2^(-1) ,也就是⼗进制 0.5 ,⼆进制 0.01 转成⼗进制就是2^-2 ,也就是⼗进制 0.25 ,以此类推。
举个例⼦,⼆进制 1010.101 转⼗进制的过程,如下图:

三、计算机是怎么存小数的?

计算机存储⼩数的采⽤的是浮点数,名字⾥的「浮点」表示⼩数点是可以浮动的。
⽐如 1000.101 这个⼆进制数,可以表示成 1.000101 x 2^3 ,类似于数学上的科学记数法。

如果⼆进制要⽤到科学记数法,同时要规范化,那么不仅要保证基数为 2,还要保证⼩数点左侧只有 1 位,⽽且必须为 1。

所以通常将 1000.101 这种⼆进制数,规格化表示成 1.000101 x 2^3 ,其中,最为关键的是 000101 和3 这两个东⻄,它就可以包含了这个⼆进制⼩数的所有信息:

  • 000101 称为尾数,即⼩数点后⾯的数字;
  • 3 称为指数,指定了⼩数点在数据中的位置;

现在绝⼤多数计算机使⽤的浮点数,⼀般采⽤的是 IEEE 制定的国际标准,这种标准形式如下图:

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数;
  • 指数位:指定了⼩数点在数据中的位置,指数可以是负数,也可以是正数,指数位的⻓度越⻓则数值的表达范围就越⼤;
  • 尾数位:⼩数点右侧的数字,也就是⼩数部分,⽐如⼆进制 1.0011 x 2^(-2),尾数部分就是 0011,⽽且**尾数的⻓度决定了这个数的精度,**因此如果要表示精度更⾼的⼩数,则就要提⾼尾数位的⻓度;

⽤ 32 位来表示的浮点数,则称为单精度浮点数(float),⽽⽤ 64 位来表示的浮点数,称为双精度浮点数(double) ,它们的结构如下:

可以得知:

  • double 的尾数部分是 52 位,float 的尾数部分是 23 位,由于同时都带有⼀个固定隐含位,所以 double 有 53 个⼆进制有效位,float 有 24 个⼆进制有效位,所以所以它们的精度在⼗进制中分别是 log10(2^53) 约等于 15.95 和 log10(2^24) 约等于 7.22 位,因此 double 的有效数字是 15~16 位,float 的有效数字是 7~8 位,这些是有效位是包含整数部分和⼩数部分;
  • double 的指数部分是 11 位,⽽ float 的指数位是 8 位,意味着 double 相⽐ float 能表示更⼤的数值范围;

那⼆进制⼩数,是如何转换成⼆进制浮点数的呢?

以 10.625 为例,看看这个数字在 float ⾥是如何存储的:

  • ⾸先,计算出 10.625 的⼆进制⼩数为 1010.101。
  • 然后把⼩数点,移动到第⼀个有效数字后⾯,即将 1010.101 右移 3 位成 1.010101 ,右移 3 位就代表+3,左移 3 位就是 -3。
  • float 中的「指数位」就跟这⾥移动的位数有关系,把移动的位数再加上「偏移量」,float 的话偏移量是127,相加后就是指数位的值了,即指数位这 8 位存的10000010 (⼗进制 130),因此可认为「指数位」相当于指明了⼩数点在数据中的位置。
  • 1.010101 这个数的⼩数点右侧的数字就是 float ⾥的「尾数位」,由于尾数位是 23 位,则后⾯要补充0,所以最终尾数位存储的数字是 01010100000000000000000 。

在算指数的时候,为什么要加上偏移量呢?

  • 指数可能是正数,也可能是负数,即指数是有符号的整数,⽽有符号整数的计算是⽐⽆符号整数麻烦的,所以为了减少不必要的麻烦,在实际存储指数的时候,需要把指数转换成⽆符号整数。

  • float 的指数部分是 8 位,IEEE 标准规定单精度浮点的指数取值范围是 -126 ~ +127 ,于是为了把指数转换成⽆符号整数,就要加个偏移量,⽐如 float 的指数偏移量是 127 ,这样指数就不会出现负数了。

  • ⽐如,指数如果是 8,则实际存储的指数是 8 + 127(偏移量)= 135,即把 135 转换为⼆进制之后再存储,⽽当我们需要计算实际的⼗进制数的时候,再把指数减去「偏移量」即可。

⼆进制浮点数的⼩数点左侧只能有 1 位,并且还只能是 1,既然这⼀位永远都是
1,那就可以不⽤存起来了。

于是就让 23 位尾数只存储⼩数部分,然后在计算时会⾃动把这个 1 加上,这样就可以节约 1 位的空间,

尾数就能多存⼀位⼩数,相应的精度就更⾼了⼀点。那么,对于我们在从 float 的⼆进制浮点数转换成⼗进制时,要考虑到这个隐含的 1,转换公式如下:

举个例⼦,我们把下图这个 float 的数据转换成⼗进制,过程如下:

四、0.1 + 0.2 == 0.3 ?

并不是所有⼩数都可以⽤「完整」的⼆进制来表示的,⽐如⼗进制 0.1 在转换成⼆进制⼩数的时候,是⼀串⽆限循环的⼆进制数,计算机是⽆法表达⽆限循环的⼆进制数的,毕竟计算机的资源是有限。

因此,计算机只能⽤「近似值」来表示该⼆进制,那么意味着计算机存放的⼩数可能不是⼀个真实值。
现在基本都是⽤ IEEE 754 规范的「单精度浮点类型」或「双精度浮点类型」来存储⼩数的,根据精度的不同,近似值也会不同。

  • 那计算机是存储 0.1 是⼀个怎么样的⼆进制浮点数呢?

可以看到,8 位指数部分是 01111011 ,23 位的尾数部分是10011001100110011001101 ,可以看到尾数部分是 0011 是⼀直循环的,只不过尾数是有⻓度限制的,所以只会显示⼀部分,所以是⼀个近似值,精度⼗分有限。

  • 0.2 的 float 浮点数:

    可以看到,8 位指数部分是 01111100 ,稍微和 0.1 的指数不同,23 位的尾数部分是
    10011001100110011001101 和 0.1 的尾数部分是相同的,也是⼀个近似值。

  • 0.1 的⼆进制浮点数转换成⼗进制的结果是 0.100000001490116119384765625 :

  • 0.2 的⼆进制浮点数转换成⼗进制的结果是 0.20000000298023223876953125 :

    这两个结果相加就是 0.300000004470348358154296875

  • 所以,在计算机中 0.1 + 0.2 并不等于完整的 0.3。这主要是因为有的⼩数⽆法可以⽤「完整」的⼆进制来表示,所以计算机⾥只能采⽤近似数的⽅式来保存,那两个近似数相加,得到的必然也是⼀个近似数。

五、总结

1、为什么负数要用补码表示?

负数之所以⽤补码的⽅式来表示,主要是为了统⼀和正数的加减法操作⼀样,毕竟数字的加减法是很常⽤的⼀个操作,就不要搞特殊化,尽量以统⼀的⽅式来运算。

2、十进制小数怎么转成二进制?

  • ⼗进制整数转⼆进制使⽤的是「除 2 取余法」
  • ⼗进制⼩数转⼆进制使⽤的是「乘 2 取整法」

3、计算机是怎么存小数的?

计算机是以浮点数的形式存储⼩数的,⼤多数计算机都是 IEEE 754 标准定义的浮点数格式,包含三个部分:

  • 符号位:表示数字是正数还是负数,为 0 表示正数,为 1 表示负数;
  • 指数位:指定了⼩数点在数据中的位置,指数可以是负数,也可以是正数,指数位的⻓度越⻓则数值的表达范围就越⼤;
  • 尾数位:⼩数点右侧的数字,也就是⼩数部分,⽐如⼆进制 1.0011 x 2^(-2),尾数部分就是 0011,⽽且尾数的⻓度决定了这个数的精度,因此如果要表示精度更⾼的⼩数,则就要提⾼尾数位的⻓度;

⽤ 32 位来表示的浮点数,则称为单精度浮点数,也就是我们编程语⾔中的 float 变量,⽽⽤ 64 位来表示的浮点数,称为双精度浮点数,也就是 double 变量。

4、0.1 + 0.2 == 0.3 吗?

不是,0.1 和 0.2 这两个数字⽤⼆进制表达会是⼀个⼀直循环的⼆进制数,⽐如 0.1 的⼆进制表示为 0.00011 0011 0011… (0011 ⽆限循环),对于计算机⽽⾔,0.1 ⽆法精确表达,这是浮点数计算造成精度损失的根源。

因此,IEEE 754 标准定义的浮点数只能根据精度舍⼊,然后⽤「近似值」来表示该⼆进制,那么意味着计算机存放的⼩数可能不是⼀个真实值。 0.1 + 0.2 并不等于完整的 0.3,这主要是因为这两个⼩数⽆法⽤「完整」的⼆进制来表示,只能根据精度舍⼊,所以计算机⾥只能采⽤近似数的⽅式来保存,那两个近似数相加,得到的必然也是⼀个近似数。

整理自小林coding所著的《图解系统》,仅做学习之用,侵删

【操作系统】为什么 0.1 + 0.2 不等于 0.3 ?相关推荐

  1. php if语句等于不等于0,java判断long不等于0

    java判断long不等于0 [2021-02-01 13:28:29]  简介: php不等于符号输入的方法:1.可以使用BASIC.PASCIC类语言的不等于符号[<>]:2.可以使用 ...

  2. 判断一个doule等于0的正确方法

    doule进行数学运算时会出现精度问题,判断double是否等于0是不能用"d==0" 要用下面的方法: public static void main(String[] args ...

  3. C语言中判断浮点数是否等于0

    1.C语言中判断浮点数是否等于0: 2.C语言中判断两个浮点数是否相等: float.double分别遵循R32-24,R64-53的标准.他们尾数的位数分别是23.52,即误差在2^-23,2^-5 ...

  4. python0.1+0.2不等于0.3_为什么0.1 + 0.2不等于0.3?

    原标题:为什么0.1 + 0.2不等于0.3? 0.1 + 0.2不等于0.3这是一个普遍的问题,例如在JS控制台输入将得到0.30000000000000004 在python的控制台也是输出这个数 ...

  5. 算法-----三数之和等于0

    三数之和 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件 且不重复的三元组. 注意:答案中不可以包含重 ...

  6. 有小数点是什么类型_为什么0.1+0.2不等于0.3?原来编程语言是这么算的……

    打开你的 Python,输入「0.1+0.2=」,结果是多少?0.30000000000000004 对不对?为什么结果不是 0.3?本文作者给出了详细的解释. 选自Medium,作者:Parul M ...

  7. Java黑皮书课后题第2章:2.3(将英尺转化为米)编写程序,读入英尺数,将其转换为米数并显示结果。1英尺等于0.305米

    2.3(将英尺转化为米)编写程序,读入英尺数,将其转换为米数并显示结果.1英尺等于0.305米 题目 题目描述 破题 代码块 方法评价 修改日志 题目 题目描述 2.3(将英尺转化为米)编写程序,读入 ...

  8. 进程P1、P2、P3、P4和P5的前趋图如下图所示。若用PV操作控制进程P1~P5并发执行的过程,则需要设置6个信号S1、S2、S3、S4,且信号量S1~S4的初值都等于0。下图中a和b处应分别填写(

    进程P1.P2.P3.P4和P5的前趋图如下图所示.若用PV操作控制进程P1-P5并发执行的过程,则需要设置6个信号S1.S2.S3.S4,且信号量S1-S4的初值都等于0.下图中a和b处应分别填写( ...

  9. 如何解决JavaScript中0.1+0.2不等于0.3

    原文转载自:https://www.cnblogs.com/weshare/archive/2018/02/20/8455470.html >console.log(0.1+0.2===0.3) ...

  10. matlab if m不等于0,matlab问题clearfor a=0.1:0.1:50for b=0.1:0.1:20for m=0.1:0.1:5

    来源:学生作业帮 编辑:作业帮 分类:综合作业 时间:2021/03/23 06:16:09 matlab问题 clear for a=0.1:0.1:50 for b=0.1:0.1:20 for ...

最新文章

  1. BOS中常用方法和类
  2. 9.IDA-重新设置函数类型、创建数组结构
  3. 记一些茅塞顿开的事情
  4. rx java定时循环_Rxjava定时器异常循环
  5. html 显图片宽度100,HTML – 如何显示内联几个 100%宽度?
  6. sql azure 语法_在Azure中使用SQL Server 2016 CTP3
  7. jetty:Address already in use: bind
  8. java的if判读_java if判断
  9. foreign 磁盘阵列_RAID组中的“Foreign”状态磁盘
  10. STM32+二维码扫描模块(QR_Scaner)实现门禁系统
  11. 为什么需要超出48K的音频采样率,以及PCM到DSD的演进
  12. Windows主机操作系统安全加固规范
  13. 崔天翼的找工作总结 zz
  14. Redis遇到过的问题(Could not get a resource from the pool)
  15. WSTMart 视频教程,WSTMart 开发文档
  16. 开始体验WinXp + VmWare Workstation + Fedora8
  17. c++11多线程编程(一):创建线程的三种方法
  18. 黑客攻防web安全实战详解笔记
  19. 中国本土化编程 汉语编程 之我见
  20. 离散傅里叶变换-DFT(FFT基础)

热门文章

  1. 计算机c盘中无法搜索文件格式,电脑C盘中programdata文件夹找不到该怎么办
  2. 阿里云因发现Log4j2漏洞未及时上报,被工信部处罚!
  3. 小米无线键盘的连接方式
  4. 使用 Parity 建立Proof-of-Authority (PoA) Ethereum Chain
  5. python爬虫爬取下厨房食谱,周末聚餐真的停不下来
  6. 泛函极值问题与变分法
  7. 使用VAE(变分自动编码器),来预测用户流失.
  8. SpringCloudGateway原理——Gateway集成eureka服务发现转发请求
  9. MIPI屏上电时序问题
  10. Transaction marked as rollbackOnly