二进制浮点数下的四舍五入


文章目录

  • 二进制浮点数下的四舍五入
    • 1. 前言
    • 2. 浮点数
      • 在解决问题之前,我们需要来了解一下什么是浮点数。
    • 3. 计算精度
    • 4. 四舍五入问题
    • 5 . 总结

1. 前言


采用IEEE-754标准表示浮点数,并不能精确表示许多实数,所以会有一些存在。本文就是对方面的问题做一个刨根揭底的探索以及摸索对应的解决方案。

2. 浮点数

在解决问题之前,我们需要来了解一下什么是浮点数。

2.1 什么是浮点数
在计算机系统的发展过程中,曾经提出过多种方法表达实数。典型的比如相对于浮点数的定点数(Fixed Point Number)。在这种表达方式中,小数点固定的位于实数所有数字中间的某个位置。定点数表达法的缺点在于其形式过于僵硬,固定的小数点位置决定了固定位数的整数部分和小数部分,不利于同时表达特别大的数或者特别小的数。最终,绝大多数现代的计算机系统采纳了所谓的浮点数表达方式。这种表达方式利用科学计数法来表达实数,即用一个尾数(Mantissa,尾数有时也称为有效数字——Significand;尾数实际上是有效数字的非正式说法),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。比如 123.45 用十进制科学计数法可以表达为 1.2345 × 102 ,其中 1.2345 为尾数,10 为基数,2 为指数。浮点数利用指数达到了浮动小数点的效果,从而可以灵活地表达更大范围的实数。

计算机中是用有限的连续字节保存浮点数的。在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。

2.2 IEEE 浮点数
计算机中是用有限的连续字节保存浮点数的。在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。很多语言都是用这种规范的浮点数表示法,javascript也不例外。

IEEE 754 指定:

两种基本的浮点格式:单精度和双精度。

IEEE 单精度格式具有 24 位有效数字精度,并总共占用 32 位。

IEEE 双精度格式具有 53 位有效数字精度,并总共占用 64 位。

2.3 范围和精度
从上面可以看到一个数在计算机里面的表示位数是有限的,儿Javascript 作为一门动态语言,其数字类型只有 number 一种。 nubmer 类型使用的就是 IEEE754 标准中的 双精度浮点数。也就是说在js里面一个数的存储空间都是固定死的。下面来看一下这个定死的空间到底有多大。

2.3 范围和精度
从上面可以看到一个数在计算机里面的表示位数是有限的,儿Javascript 作为一门动态语言,其数字类型只有 number 一种。 nubmer 类型使用的就是 IEEE754 标准中的 双精度浮点数。也就是说在js里面一个数的存储空间都是固定死的。下面来看一下这个定死的空间到底有多大。

从上图可以看出 在js里面只有52位用来存放数据。
那么问题来了,如果一个数52位存储空间不够,也就是二进制也会出现想十进制一样的无限数的时候,会发生什么事情呢?

IEEE754采用的浮点数舍入规则有时被称为舍入到偶数(Round to Even)

这有点像我们熟悉的十进制的四舍五入,即不足一半则舍,一半以上(包括一半)则进。不过对于二进制浮点数而言,还多一条规矩,就是当需要舍入的值刚好是一半时,不是简单地进,而是在前后两个等距接近的可保存的值中,取其中最后一位有效数字为零者。

3. 计算精度

在解决问题之前,我们还需要先理解计算过程 这里用一个最经典的例子先来说明一下js中数据计算的过程

0.1 + 0.2
复制代码
很多人都看到过这个表达式,那么这个表达式背后究竟发生了什么过程呢,请看本胖一步步说来

A 十进制转二进制
第一步浏览器会将我们看到的十进制0.1以及0.2都转为二级制的0.1和0.2

对于十进制转二进制,大部分人都知道整数是除2取余,逆序排列 直到商为0时为止。但是小数呢,规则是和整数不一样的,规则如下

乘2取整,顺序排列 直到积中的小数部分为零
复制代码
有了规则,我们现在来对0.1,0.2做一个转化

0.1转为二进制

二进制0.00011001100110011…(循环0011)
尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
计算机存储为:0 00000000100 10011001100110011…11001
因为尾数最多52位,所以实际存储的值为0.0001100110011001100110011001100110011001100110011001101
复制代码
0.2转为二进制

二进制0.0011001100110011…(循环0011)
尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
存储为:0 00000000011 10011001100110011…11001
因为尾数最多52位,所以实际存储的值为0.001100110011001100110011001100110011001100110011001101
复制代码
0.1的二进制 + 0.2的二进制 这里要先说一下二进制的加法规则

计算机计算二进制加法是分三部,第一步为将两个加数转换为二进制数,计算两个加数不需要进位的和,得出的结果。
第二部将两个加数进行与运算(&)。
第三部利用与运算得到结果进行左移运算(<<)(同时为计算两个加数需要进位的和),得出结果。将或异运算的结果和左移运算的结果作为两个新的加数,重复此操作。直到当与运算的结果为0,则异或运算的结果则为两个加数的和所对应的二进制数。
复制代码
按照上述规则,我们已经将0.1,0.2都完成了第一步,现在要进行第二三步。最终得到如下结果

0.01001100110011001100110011001100110011001100110011001100
复制代码
然后将二进制所得的结果再转为十进制的表示,下面是二进制小数转为十进制的规则

整数部分是从右到左用二进制的每个数去乘以2的相应次方,小数部分则是从左往右开始计算
复制代码
最终得到的结果就是0.30000000000000004

好了,一个0.1+0.2的计算过程大概就是这些过程。

4. 四舍五入问题

4.1 问题
现在,我们就可以来问答一开始的问题了,为什么会出现文章一开始计算的问题情况了。

1 65.00(商品价格)* 0.119(税费) = 7.734999999999999 !== 7.735
复制代码
2 (65.00(商品价格)* 0.119(税费)).toFixed(2) = 7.73 != 7.74
复制代码
第一个相乘为什么不等于7.735呢,本胖在第三节就给出了解释,现在我们来说一下第二个四舍五入精度问题。

对一个数进行四舍五入操作的时候,也是需要先将这个我们理解的十进制数转为计算机理解的二进制数,然后用计算机的四舍五入规则(2.3 范围和精度中已经说明)进行对应的操作。

当65.00(商品价格)* 0.119(税费)的二进制存储的时候就已经是一个近似值了,然后再用二进制的四舍五入进行操作最后将得到的结果再转为十进制当然会存在一个误差。

看到这里是不是就明白了为什么在调用toFixed()方法的时候会存在误差,因为我们看到的都是十进制的世界,而真实运算的却是二进制的世界,不同的二个世界,不同的规则。

4.2 解决方案
前面说到十进制的小数在转为二进制的时候很容易出现无法精确表示的情况,但是十进制整数,在转为二进制的时候是都可以精确表示的,因为十进制整数转二进制的规则如下

除2取余,逆序排列,直到商为0时为止
复制代码
所以机智的同学就会想到下面的办法

把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)
复制代码
按照上面的办法可以写出下面的函数

function toFixed(num, s) {
var times = Math.pow(10, s)
var des = num * times
des = Math.round(des) / times
return des + ‘’
}
复制代码
注意这里用了Math.round()这个方法将数字转为最接近的整数,如果用parseInt()的话需要手动加0.5。下面是这3个方法的主要区别。

  1. Math.round

作用:四舍五入,返回参数+0.5后,向下取整。

Math.round(5.57)  //返回6

Math.round(2.4)   //返回2

Math.round(-1.5)  //返回-1

Math.round(-5.8)  //返回-6

复制代码
2.parseInt

作用:解析一个字符串,并返回一个整数,这里可以简单理解成返回舍去参数的小数部分后的整数。

parseInt(5.57)  //返回5

parseInt(2.4)  //返回2

parseInt(-1.5)  //返回-1

parseInt(-5.8)  //返回-5
复制代码

5 . 总结

1.看似有穷的数字, 在计算机的二进制表示里却是无穷的,由于存储位数限制因此存在“舍去”,精度丢失就发生了。

2.解决精度丢失的方法就是把小数放到位整数(乘倍数),再缩小回原来倍数(除倍数)

二进制浮点数下的四舍五入相关推荐

  1. IEEE 754二进制浮点数算术标准

    可能很多人都遇到过浮点数精度丢失的问题,下面以JavaScript为例. 1 - 0.9 = 0.09999999999999998 纳尼,不应该是0.1么,怎么变成0.099999999999999 ...

  2. 二进制浮点数以及二进制浮点数算术运算

    二进制浮点数以及二进制浮点数算术运算 二进制浮点数表示 半精度浮点数 单精度浮点数 双精度浮点数 特殊情况 浮点数的运算步骤 一.对阶 二.尾数运算 三.结果规格化 左规操作 右规操作 四. 舍入处理 ...

  3. 浮点数转换为整数四舍五入_定义宏以将浮点值四舍五入为C中最接近的整数

    浮点数转换为整数四舍五入 Given a float value and we have to round the value to the nearest integer with the help ...

  4. c语言浮点数高精度求平方根,快速高精度的二进制浮点数开平方算法

    1引盲开平方运算在用徽机.单片机等构成的实时控制系统和测量仪器中有着广泛的应用.开平方运算的实现方法有多种:如牛顿迭代法.查表法.直线逼近法(线性化方法)和减奇数法等.对于查表法,当被开方数变化范围较 ...

  5. mysql二进制包下的support-files文件夹

    可能很多习惯用rpm包的人在部署mysql的二进制包后并不懂的怎么使用(具体mysql二进制部署可参看http://jim123.blog.51cto.com/4763600/1835010),其实不 ...

  6. python 向上、向下、四舍五入取整方法 round圆整

    import math #向上取整 >>> math.ceil(2.3) 3 >>> math.ceil(2.6) 3 #向下取整 >>> mat ...

  7. python下的四舍五入

    python下的函数round可以四舍五入,但函数int就只能向下取整数,以下代码可以实现四舍五入 int(round(x))或int(2*x)/2+int(2*x)%2

  8. c语言x在二进制表示下1的个数,算法:计算十进制数字在二进制表示1的个数,...

    算法:计算十进制数字在二进制表示1的个数, 题目一 计算十进制数字在二进制表示 1 的个数 举个例子: 十进制数字为 1 时,它的二进制表示是 001,二进制表示 1 的个数为 1: 十进制数字为 2 ...

  9. mysql8.0. linux二进制_linux下安装mysql8.0(二进制方式)

    1.下载安装介质 官网下载 我这里下载的是8.0.17 mysql-8.0.17-linux-glibc2.12-x86_64.tar.xz 2.创建mysql用户和用户组 #groupadd mys ...

最新文章

  1. CCAI2018演讲实录 | 蒲慕明:脑科学与类脑机器学习
  2. condition.await
  3. 【python 11】super()
  4. WebApiClient与Asp.net core DI的结合
  5. pythonset是什么类型的游戏_Python集合(set)类型的操作
  6. 第五十四篇 Linux相关——远程连接SSH
  7. 绕过宝塔禁止的php函数,宝塔disable functions函数全被禁命令执行+加域服务器如何无限制执行命令...
  8. Javascript原型钩沉
  9. 关于封装的一个小问题和TA的例子
  10. 上传图片的表单java代码_java模拟post方式提交表单实现图片上传(示例代码)
  11. 点击空白处隐藏指定dom元素(纯javascript方法)
  12. js函数、事件、补充知识
  13. matlab+yalmip+mosek/cplex安装配置
  14. SSS1700设计方案|SSS1700中文说明书
  15. 会议室预定小程序[叮当会议]
  16. zbt (Steam游戏道具)自动发货
  17. python374安装教程_Centos7.6安装工具(5)--编译安装python374
  18. 前端upload标签使用方法
  19. 2020年团体程序设计天梯赛-总决赛-题目
  20. 先打基础,再赶时髦:摆弄新工具之前,先把手艺学好

热门文章

  1. 傅里叶变换在图像处理的意义和应用
  2. 申请QQ密码保护,保护号码安全!(转)
  3. 小公司老板的日常管理,希望能让创业朋友学到东西
  4. 使用html2canvas完成页面截屏并保存为图片
  5. ffmpeg为mkv封装格式的音视频文件添加内挂字幕
  6. Python 求奇数分之一序列前N项和
  7. java 微信海报的实现
  8. 计算机房考研英语考试时间按,考研各科目答题时间分配
  9. 我学编程全靠B站了,真香(第二期)
  10. python笔记10:数据处理之去除空格