[JavaScript]javascript Floating-Point calculation bug

摘要:[Javascript]javascript Floating-Point calculation bug
Javascript在做Float数值的运算有时候为什么连一个简单的两数相乘都会出错呢?
为什么Javascript的浮点数计算会有这样的差异呢?又该如何解决呢?
让本篇文章带你了解更多吧


前阵子在帮同事改一段以前的程序的时候发现Javascript在做Float数值的运算时有bug,会导致简单的计算出问题,当下去找了一下数据有几个处理方式整理给大家。

Problem Description

在开始前我们直接看下面的范例就知道问题在哪边

JS Bin

这边三个简单的计算,你可以发现前面两个都正确,第三个结果就怪了。 小学的99乘法表告诉我们99是81,但这个结果的尾数竟然不是1....就知道见鬼了

为什么Javascript的浮点数计算会有这样的差异呢?

因为电脑没办法正确的显示0.1,0.2,0.3这样的浮点数(因为数值都是0101的组成),所以透过0101组成浮点数时就产生了误差。我们看到的0.1其实是已经经过进制后的结果(原先可能是0.1000000000000001这样的值),所以当我们做运算后就会产生更大的误差。

对于浮点数的基本组成与相关问题可以参考这篇文章,解释得非常详细。

这边帮大家在Google大神的协助下找了几张图来参考。

首先一个简单的浮点数可以分为两部分:

而透过二进制表示法(IEEE 754)来存放时则如下:

实际的运算逻辑flow大致可以参考下图

flow

Solution without plugin

简单来讲我们可以用Number.prototype.toFixed()这个数值类型的function来帮我们确认要计算到多精准的小数位

开始前我们先参考MDN给我们的说明

Method of Number

Implemented in JavaScript 1.5

ECMAScript Edition ECMAScript 3rd Edition

Syntax

number.toFixed( [digits] )

Parameter

digits The number of digits to appear after the decimal point; this may be a value between 0 and 20, inclusive, and implementations may optionally support a larger range of values. If this argument is omitted, it is treated as 0.

Returns

A string representation of number that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary, and the fractional part is padded with zeros if necessary so that it has the specified length. If number is greater than 1e+21, this method simply calls Number.toString() and returns a string in exponential notation.

Throws

RangeError If digits is too small or too large. Values between 0 and 20, inclusive, will not cause a RangeError. Implementations are allowed to support larger and smaller values as well. TypeError If this method is invoked on an object that is not a Number.

如上面的说明,由于他是ECMAScript的内建语法所以我们不需要特别用什么套件,只要确任浏览器支持就可以了(目前全部浏览器都支持)

我们将刚刚的一开始的三个范例改写如下

JS Bin

如此一来我们就可以正确地显示浮点数运算过后的值了。

从解法我们发现如果用 number.toFixed( [digits] )) 这样的解法需要先知道到底结果是小数点第几位,但实际上我们有很多情况会不知道该数值到底第几位.所以可能还要把多算出来的0给处理掉,如下:

JS Bin

Solution using Math.js

这边介绍一个方便的javascript plugin来帮大家解决数学计算的问题

使用上很简单,只要记得加上math.js到你的页面上即可,没有其他library dependency。

下面就快速的把先前的范例用Math.js呈现给大家看

JS Bin

这边可以看到透过Math.js我们就可以很简单地取到精准度为小数点后10位的结果也不会有多余的0,是不是很方便呢!

其实Math.js还有很多很酷的功能,他也支持Nodejs与其他框架使用,大家赶快把它列入项目必装的套件吧。

Math.js

结语

老实说学生时代只有印象中听过浮点数计算透过二进制保存有误差,但我没想到学问真的很大....也没想到出社会还会看到这类型问题,也很感谢有遇到这样的问题让自已能够在多收获一些知识。



如果觉得文章还不错麻烦请在文章最上面给予推荐,你的支持是小弟继续努力产出的动力!