可能很多人都遇到过浮点数精度丢失的问题,下面以JavaScript为例。

1 - 0.9 = 0.09999999999999998

纳尼,不应该是0.1么,怎么变成0.09999999999999998呢?这就要从ECMAScript标准讲起了。

ECMAScript 并不像其他编程语言一样对数值类型进行比较具体的划分。ECMAScript 中并不区分整数和浮点数,也不区分不同长度的整数和浮点数。

ECMAScript 中的 Number 类型始终使用 64 位双精度浮点数来表示数值。这一方面使得处理起来变得简单,另外一方面也限制了可以表示的数值的范围。ECMAScript 中 Number 类型的值的个数是 264-253+3。个数后面的“+3”表示的是 Number 类型的 3 个特殊值,分别是 NaN、+Infinity 和 -Infinity。

NaN 的含义是“不是一个数字(Not-A-Number)”。在代码中可以直接通过“NaN”的方式来引用这个值。代码中与数值相关的计算的结果也可能是 NaN。一般来说,对于 ECMAScript 语言中的操作符,如果其中一个操作数为 NaN,那么计算结果为 NaN。当需要判断一个变量引用 a 是否为 NaN 时,只需要判断 a !== a 是否为 true 即可。+Infinity 和 -Infinity 分别表示正无穷大和负无穷大,可以在代码中直接引用,也可能是某些数值运算的结果。如运算“3 / 0”的结果是 Infinity。除了这 3 个特殊值之外,剩下的数值中一半是正数,一半是负数。数值 0 也有正数和负数两种形式,称为正 0 和负 0,分别用 +0 和 -0 来表示。

讲完了JavaScript的实现标准,现在来了解一下目前最通用的IEEE二进制浮点数算术标准(IEEE Standard for Binary Floating-Point Arithmetic,简称 IEEE 754 标准)。

IEEE 754 标准的主要起草者是加州大学伯克利分校数学系的 William Kahan 教 授,他帮助 Intel 公司设计了 8087 浮点处理器,并以此为基础形成了 IEEE 754 标 准, Kahan 教授也因此获得了 1987 年的图灵奖。Kahan教授的个人主页:https://people.eecs.berkeley.edu/~wkahan/

IEEE 754标准中关于浮点数的四种格式:

  • 两种基本的浮点数:单精确度 (32 位字长) 和双精确度 (64 位字长)。其中单精度格式具有 24 位有效数字,而双精度格式具有 53 位有效数字,相对于十进制来说,分别是 7 位 (224 ≈ 107) 和 16 位 (253 ≈ 1016) 有效数字。
  • 两种扩展的浮点数:单精度扩展双精度扩展。此标准并未规定扩展格式的精度和大小,但它指定了最小精度和大小:单精度扩展需 43 位字长以上,双精确度扩展需 79 位字长以上 (64 位有效数字)。单精度扩展很 少使用,而对于双精确度扩展,不同的机器构架中有不同的规定,有的为80 位字长 (X86),有的为 128 位字长 (SPARC)。

这里我们只简单介绍单、双精度,其中重点介绍单精度,双精度与单精度原理是一样的,只是表示的位数长度不同。

浮点数的组成(sign 符号、exponent 指数、fraction 尾数):

IEEE标准采用类似于科学计数法的方式表示浮点小数,即我们将每一个浮点数表示为 V = (-1)s * M * 2E

1)(-1)s表示符号位,当s=0,V为正数;当s=1,V为负数。
2)M 表示有效数字,1 ≤ M < 2。
3)2E 表示指数位。

从公式  V = (-1)s * M * 2E  我们可以得出:

1) 符号位:确定正、负。

2) 尾数的位数:确定精度。

3) 指数的位数:确定所能表示的数的范围。

所谓科学计数法,我举一个例子(左移/右移指数的多少位,我们知道在二进制中左移一位表示乘以2,右移一位表示除以2,当移动N位时就是2N,N可为正也可为负)。

18800000000 = 1.88 x  1010

0.000000000188 = 1.88 x 10 -10

64位的指数位长度为11,32位的指数长度位为8,所以64位双精度所能表示的范围远大于32位。

二进制浮点数是以符号数值表示法的格式存储 —— 最高有效位被指定为符号位(sign bit);“指数部分”,即次高有效的e个比特,存储指数部分;最后剩下的f个低有效位的比特,存储“有效数”(significand)的小数部分(在非规约形式下整数部分默认为0,其他情况下一律默认为1)。

指数偏移值(exponent bias),是指浮点数表示法中的指数域的编码值为指数的实际值加上某个固定的值,IEEE 754标准规定该固定值为 2e-1,其中的 e 为存储指数的比特的长度。单精度为8,双精度为11。所以单精度的固定偏移值是28-1 – 1 = 128 – 1 = 127,而双精度的固定偏移值是211-1 – 1 = 1024 – 1 = 1024。

指数实际的存储:指数的值可能为负数,如果采用补码表示的话,全体符号位S和Exp自身的符号位将导致不能简单的进行大小比较。正因为如此,指数部分通常采用一个无符号的正数值存储。单精度的指数部分是-126 ~ +127,加上固定偏移值127,指数值的大小从1 ~ 254(0和255是特殊值)。浮点小数计算时,指数值减去固定偏移值将是实际的指数大小。

采用指数的实际值加上固定的偏移值的办法表示浮点数的指数,好处是可以用长度为 e 个比特的无符号整数来表示所有的指数取值,这使得两个浮点数的指数大小的比较更为容易,实际上可以按照字典序比较两个浮点表示的大小。这种移码表示的指数部分,中文称作阶码

针对阶码E的值,浮点数的值可以分为三种不同的类型:规格化数(正规数)、非规格化数(次正规数)、特殊值。

单精度格式位模式

0 < e < 255

(−1)s × 1. f × 2e−127  (正规数)

e = 0, f ≠ 0

(−1)s × 0. f × 2−126  (次正规数)

e = 0, f = 0

(−1)s × 0.0 (有符号的零)

s = 0, e = 255, f = 0

+Infinity (正元穷大,特殊值)

s = 1, e = 255, f = 0

+Infinity (负元穷大,特殊值)

e = 255, f ≠ 0

NaN (非数、非确定值,特殊值)

这么讲,还是有点晕,我们通过二个数字的示例来详细说明。

第一个示例:263.3,先拆解一下:263.3 => 整数 263 + 小数0.3

正式开始推算前,先介绍三种十进制转二进制的三种方法:除基取余法、减权定位法、乘基取整法。

三种方法来自中科大《计算机组成原理 | 第6章 计算机的运算方法》的相关资料,这里只介绍除基取余法,因为我们接下来计算的就是用的这个方法。

除基取余法:把给定的数除以基数,取余数作为最低位的系数,然后继续将商部分除以基数,余数作为次低位系数,重复操作,直至商为0。

以下推导过程我在纸上写出来了。

计算结果与官网的进行对比如下。IEEE 754 Floating Point Converter>>

最终的二进制表示:"0100,0011,1000,0011,1010,0110,0110,0110"

同理45.45也可以这样进行计算。(45)10 = (101101)2

45.45 –> 101101.0111001100…(1100循环)

可以使用toString查看十进制转二进制的结果,与上面计算出来的结果进行对比。

Number(45.45).toString(2)
"101101.0111001100110011001100110011001100110011001101"

套公式:V = (-1)s * M * 2E

S = 0  (0)

E = 5  (127 + 5 = 132 –> 10000100)

M = 011010111001100… (1100循环)

最终的二进制表示:"0100,0010,0011,0101,1100,1100,1100,1101"

注意最后一位,变成了1101,进行了舍入操作,上面的263.3也进行了舍入操作。

舍入的规则是怎么定义的呢,我查了很多资料,暂时还没有弄的特别清楚。我搜索到这样一份PPT,供大家参考,其它资料其实与这个说法类似,清华大学的《浮点数误差与误差复杂度》

因为表示方法限制了浮点数的范围和精度,浮点运算只能近似地表示实数运算。IEEE 浮点数格式定义了四种不同的的舍入方式:

1) 向偶数舍入(默认,不是四舍五入)

2) 向零舍入 (取整)

3) 向上舍入 (ceil)

4) 向下舍入 (floor)

向0(截断)舍入:C/C++的类型转换。(int) 1.324 = 1,(int) -1.324 = -1;

向负无穷大(向下)舍入:C/C++函数floor()。例如:floor(1.324) = 1,floor(-1.324) = -2。

向正无穷大(向上)舍入:C/C++函数ceil()。ceil(1.324) = 2。Ceil(-1.324) = -1;

正是因为舍入的存在,误差的存就就成了必然,精确只是偶然的。做数据算法,惟一能做的就是误差不积累。

关于浮点数,还有一些知识点是没有讲的,例如浮点异常:无效运算、被零除、上溢、下溢和不精确,以及相关的一些运算示例。

参考资料:

深入探讨 ECMAScript 规范第五版

IEEE-754 Floating Point Converter

Decimal to IEEE 754 Floating Point Representation

清华大学的《浮点数误差与误差复杂度》

南京大学的《数据的机器信表示》

中国科技大学《计算机组成原理 | 第6章 计算机的运算方法》

华东师范大学《IEEE浮点运算标准》

计算机组成原理课件/CH02-5浮点数(2.9).pdf

IEEE 754二进制浮点数算术标准的更多相关文章

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

    整理自IEEE 754 IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0) ...

  2. IEEE二进制浮点数算术标准学习

    看到有网上有个项目是要求将浮点数用二进制表示出来,需要用IEEE754标准,查了查维基和深入理解计算机系统,重新学习了一遍浮点数在计算机中的表示和内存中的存储, 先简单的做个笔记,后面需要更深入的理解 ...

  3. IEEE754二进制浮点数算术标准

    对于32位浮点数 sign: 符号,1位 exponent: 指数,8位,偏码 fraction: 分数,23位,原码 特殊值   指数域的编码值 = 指数的实际值 + 127 这样按照字典序的顺序就 ...

  4. IEEE 754 浮点数在计算机中的表示方法

    IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denorma ...

  5. IEEE 754标准--维基百科

    IEEE二进制浮点数算术标准(IEEE 754) 是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用.这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denorm ...

  6. 【算法】解析IEEE 754 标准

    目录结构: contents structure [-] 浮点数的存储过程 次正规数(Denormalized Number) 零(zero) 非数值(NaN) 无穷大(infinity) 除数为0. ...

  7. 玉伯的一道课后题题解(关于 IEEE 754 双精度浮点型精度损失)

    前文 的最后给出了玉伯的一道课后题,今天我们来讲讲这题的思路. 题目是这样的: Number.MAX_VALUE + 1 == Number.MAX_VALUE; Number.MAX_VALUE + ...

  8. 打印一个浮点数组,会输出字符串"Hello, world“ & 浮点数的二进制表示(IEEE 754标准)

    #include <stdio.h> #include<stdlib.h> int main() { float a[3] = { 1143139122437582505939 ...

  9. IEEE 754浮点数表示标准

    二进制数的科学计数法 C++中使用的浮点数包括采用的是IEEE标准下的浮点数表示方法.我们知道在数学中可以将任何十进制的数写成以10为底的科学计数法的形式,如下 其中显而易见,因为如果a比10大或者比 ...

随机推荐

  1. PYTHON-函数的定义与调用,返回值,和参数-练习

    # day10函数的定义调用和参数作业# 1.写函数,用户传入修改的文件名.与要修改的内容,执行函数,完成批量修改操作# def modify_file(filename,old,new):# imp ...

  2. vue2的缓存问题(非原创)

    keep-alive是vue内置的一个组件,可以使被它包含的组件处于保留状态,或避免被重新渲染. 用法: 运行结果描述: input输入框内,路由切换输入框内部的内容不会发生改变. 常见的用法:(下图 ...

  3. spoj New Distinct Substrings

    vjudge原地爆炸... 题意:求一个字符串不同的子串的个数 策略:后缀数组 利用后缀数组的sa和height两个功能强大的数组,我们可以实现上述操作 首先有个很显然的结论:一个字符串的所有子串=它 ...

  4. python实现的椭圆曲线加密

    我也看得云里雾里, 但是ECC和RSA并列为非对称加密双雄, 还是很有必要了解一下的. RSA是用质数分解,ECC是用离散的椭圆方程解,安全度更高. 而且,这个ECC的加法乘法规则,和普通都不一样, ...

  5. Hibernate的主配置文件hibernate.cfg.xml

    1:Hibernate的主配置文件的名字必须是hibernate.cfg.xml(主要配置文件中主要配置:数据库连接信息,其他参数,映射信息):常用配置查看源码:Hibernate\hibernate ...

  6. Gitlab管理用户、组、权限(一)

    && 内容: 添加用户 创建组 用户权限 保护主要分支 配置项目的可见性 移除用户 一. 添加用户 1. 用root 管理员登陆. 2. 点击管理区域 3. 点击New User 4. ...

  7. POJ2891 Strange Way to Express Integers 扩展欧几里德 中国剩余定理

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - POJ2891 题意概括 给出k个同余方程组:x mod ai = ri.求x的最小正值.如果不存在这样的x, ...

  8. 【目录】LeetCode Java实现

    这里记录一下自己刷的LeetCode题目. 有些博客用英文阐述自己的思路和收获,相当于练习一下英文的表达能力. 比较好的题目有加粗. 1. Two Sum 3. Longest Substring W ...

  9. ZooKeeper典型应用场景:分布式锁

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致 ...

  10. VSCode从非根目录编译golang程序(转)

    1.问题提出 “习惯在项目目录里建src放源码文件,根目录里放配置文件或者别的什么,在交付时直接忽视掉src目录就行了,但vscode好像不能这样愉快的玩耍...”??? 要实现把源码放到src目录下 ...