|
秒懂Javascript浮点数精度缺失原理
秒懂Javascript浮点数精度缺失原理
陈祥芬@贝壳找房
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2020年10月14日 20:21
如果我问你,0.1+0.2等于多少,是不是有种想让我吃药的冲动?哈哈,最近在项目中做浮点数计算的时候,还真遇到了一些看起来看起来不合常理的事情,例如:0.1+0.2控制台打印:0.300000000000000041.11x100控制台打印:111.00000000000001哈哈,博学深邃的你又说了,这是js自身浮点数计算存在的bug,但是为什么会存在这个bug呢,客官先别给我答案,给个机会让我去一探究竟吧。1 揭开神秘的面纱在《Javascript高级程序设计》第三版,第28页中有这么一段话:关于浮点数值计算会产生舍入误差的问题,有一点需要明确:这是使用基于IEEE754 数值的浮点计算的通病,ECMAScript 并非独此一家;其他使用相同数值格式的语言也存在这个问题看到这里,我们知道了,浮点数计算产生的误差和IEEE754肯定有着不可磨灭的渊源,那么IEEE754是个什么神秘代号,别急,咱们慢慢来说。1.1 何为IEEE75420 世纪 80 年代之前,计算机制造商们根据自己的需要来设计浮点数的表示规则,由于浮点数没有统一的表示标准,造成代码的移植性很低。后来,IEEE(Institute of Electrical and Electronics Engineers,电子电气工程师协会),制订了二进制浮点运算标准 IEEE 754(IEEE Standard for Binary Floating-Point Arithmetic,ANSI/IEEE Std 754-1985),并被广泛使用,也改善了代码的可移植性。IEEE 754规定了四种表示浮点数值的方式:单精度(32位)、双精度(64位)、延伸单精度与延伸双精度。其浮点数表示遵循科学记数法规范,采用{S,E,M}来表示一个数V ,并限定指数的底为2,即:上式(记为式1)中:- 符号位 S(Sign),0代表正数,1代表负数。- 指数位 E(Exponent)是 2 的幂,它的作用是对浮点数加权。- 尾数位(Mantissa)是二进制小数,其取值范围为[0,2)。1.2 用IEEE754表示JavaScript中的数字JavaScript 中的数字类型只有 Number 一种,这种类型使用IEEE754格式中的 “双精度浮点数” 来表示一个数字,不区分整数和浮点数。而双精度的浮点数使用 64 位固定长度来表示,也即S+E+M=64bit,各部分所占bit之和为64bit,如图1所示:从图1中,不难发现:符号位S占据1bit,若V为负数那么S=1,反之S=0。指数位E占据11bit,E是一个无符号整数,其取值范围是 0~2047(2^11)。但是科学计数法中允许小数点进行左右浮动以达到需要,也即指数可以根据小数的点浮动做正负值匹配,以保持数字V的大小不变。因此,若小数点右浮动则记E=[0,1022] 表示负指数,反之,用E=[1024,2047]表示正指数,如果小数点不进行浮动,那么E则取1023。尾数位M占52bit,若二进制尾数的位数大于52,那么超出的部分自动进一舍零(第53bit是1那么进位,否则舍去)。由于IEEE 754对浮点数的表示遵循科学记数法的规范,因此整数部分只能是1,为了提高数值的精度,所以整数部分在存储的时候会被舍去,只存储后面的小数部分。那么,针对双精度浮点数的情况,IEEE754双精度浮点数的描述,可改写为式2,如下:说到这,咱们基本上清楚了,JavaScript中Number类型的数据,均采用IEEE754标准中的双精度格式来描述(以上),如果结果存在误差,很有可能是尾数位超过了52,做了近似处理。下面,咱们结合公式举例说明。在举例之前,咱们先在脑海中大致构造一个数据格式转换的流程图:栗子1:十进制2.5精度无损(1)原始数据转二进制十进制浮点数2.5,转换成二进制得:10.1(2)移动小数点根据科学计数法规范,将小数点左浮动1位,得:1.01,则e=1(3)对号入座由于2.5为正数,则S=0;由式2可知,E=e+1023=1024(10000000000);将1.01中的整数部分1舍掉(原因前面有说明),得M=01,扩充至52bit得:0100000000000000000000000000000000000000000000000000因此, 2.5=(-1)^0 X(1.01)X 2^1。为了更形象的展示,咱们使用工具,得到下图:(4)反推与原始数据比较1.01小数点右浮动一位得->10.1->转十进制->2.5,没有精度损失。由于2.5的M部分实际上有效部分只有2位,2
|
|