@tangchao
2018-09-28T10:46:16.000000Z
字数 4413
阅读 510
技术分享
编程语言大都有这样的一个问题,浮点运算,会出现问题:
0.1 + 0.2 //0.30000000000000004
甚至连语言本身的 Math
里面的函数也会存在问题:
Math.pow(0.7, 2) //0.48999999999999994
Math.sqrt(0.1156) //0.33999999999999997
使用比较符号 ><
这些,如果有不是数字格式的情况,将会有自动转化,而且存在非常多不同的情况:
'10.1' > 2.1 //true
'10.1' > '2.1' //false
还有如 null, undefined, ''
在进行比较时,不报错,只显示 false:
null >= 'a' //false
null <= 'a' //false
这在数据来源于用户操作或者 ajax 请求时,特别容易出现错误。
在进行四舍五入的时候,和我们常规认识的“绝对值四舍五入再带上符号”不符合:
Math.round(11.5) //12
Math.round(-11.5) //-11
虽然存在这些语言本身的问题,但是测试不会管啊,产品不会管啊。任务不会变啊。java 中有一个 BigDecimal
类。而 js 本身不带有这个类,需要自己找一些库来实现。
网上主要有 mathjs, big.js, bignumber.js, decimal.js。后面三个是一位作者写的。
这几个库各有各的优势。我在这里主要介绍 bignumber.js。
可以直接通过 <script>
标签引入 bignumber.js,也可以使用 AMD 或者 commonjs 模块规范引入。之后就可以用构造函数生成一个 BigNumber
的实例:
var x = new BigNumber(123.4567)
x.toString() //'123.4567'
这个实例本身是一个对象,记录着这个数的信息;所以,所有的功能,不是通过运算符号,而是通过方法去实现的。
同时,BigNumber
构造函数还拥有一些实用的静态方法。
为了写法方便,先定义两个函数:
function b(val) {
return new BigNumber(val);
}
小数相加:
0.1 + 0.2 //0.30000000000000004
b(0.1).plus(0.2).toString() //'0.3'
小数相减:
0.3 - 0.2 //0.09999999999999998
(0.3*10 - 0.2*10) / 10 //0.1
b(0.3).minus(0.2).toString() //'0.1'
除法:
355 / 113 //3.1415929203539825
b(355).div(113).toString() //'3.14159292035398230088'
乘法:
0.6 * 3 //1.7999999999999998
b(0.6).times(3).toString() //'1.8'
取模:
1 % 0.9 //0.09999999999999998
b(1).mod(0.9).toString() //'0.1'
幂:
Math.pow(0.7, 2) //0.48999999999999994
b(0.7).pow(2).toString() //'0.49'
平方根:
Math.sqrt(0.1156) //0.33999999999999997
b(0.1156).squareRoot().toString() //'0.34'
调整小数位数,如果不传参,则给 get 方法,获取小数位数
b('314.15929203539825').dp(2).toString() //'314.16'
b('314.15929203539825').dp() //14
调整精度,如果不传参,则给 get 方法,获取小数位数:
b('314.15929203539825').sd(2).toString() //'310'
b('314.15929203539825').sd() //17
小数位数和精度是比较容易弄混的。一个是小数点后的数字保留几位,一个是所有数字保留几位。
调整小数点位置:
b('314.15929203539825').shiftedBy(-2).toString() //'3.1415929203539825'
取反:
b('314.15929203539825').negated().toString() //'-314.15929203539825'
比较主要是:gt
, gte
, lt
, lte
, eq
方法,除此之外还有 comparedTo
。
'10.1' > 2.1 //true
'10.1' > '2.1' //false
b('10.1').gt('2.1') //true
其余的以 is
开头的方法,大都是用来判断的。比如:
b(-0).isPositive() //false
b(0).isPositive() //true
上面的计算和调整类方法,都是返回一个 BigNumber 的实例,用于下一步的操作。所以可以进行链式调用。
我想要得到一个值,可以通过输出方法来输出一个想要的值。to
开头的方法和 valueOf
就是相应的输出方法。
最常用:toString()
,接受进制,默认为 10 进制。然后输出其值。
b('3141.5929203539825').toString() //'3141.5929203539825'
toFixed()
,接收一个参数,保留的小数位数。
b('3141.5929203539825').toFixed(2) //'3141.59'
toFormat()
,接收一个参数,保留的小数位数,转化数字格式化的字符串。
b('3141.5929203539825').toFormat(2) //'3,141.59'
我认为 bignumber.js 最大的亮点,是可以选择的修约规则。一共有九种。其中 0 到 6 和 java 的 BigDecimal
类规则一致。而 7 和 8 则是独有的。
Property | Value | Description |
---|---|---|
ROUND_UP | 0 | 远离 0 |
ROUND_DOWN | 1 | 靠近 0 |
ROUND_CEIL | 2 | 往正无穷,同 Math.ceil 的策略 |
ROUND_FLOOR | 3 | 往负无穷,同 Math.floor 的策略 |
ROUND_HALF_UP | 4 | 默认,绝对值四舍五入再带上符号,同 number.toFixed() 策略 |
ROUND_HALF_DOWN | 5 | 绝对值五舍六入后再带上符号 |
ROUND_HALF_EVEN | 6 | 绝对值四舍六入五成双再带上符号,另外一种常规标准 |
ROUND_HALF_CEIL | 7 | 五往正无穷入,同 Math.round 的策略 |
ROUND_HALF_FLOOR | 8 | 五往负无穷舍,和 Math.round 的策略刚好相反 |
修约规则可以用在输出方法 toFixed
, toFormat
的第二个参数中,也可以用在 dp
, sd
的第二个参数中,不填则取默认值。
在调用的时候,既可以写常量属性名,也可以写数字,但是建议写常量属性,这样更具有可读性。
Math.ceil(11.5) //12
Math.ceil(-11.5) //-11
Math.floor(11.5) //11
Math.floor(-11.5) //-12
Math.round(11.5) //12
Math.round(-11.5) //-11
parseFloat(11.5).toFixed(0) //'12'
parseFloat(-11.5).toFixed(0) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_UP) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_UP) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_DOWN) //'11'
b(-11.5).toFixed(0, BigNumber.ROUND_DOWN) //'-11'
b(11.5).toFixed(0, BigNumber.ROUND_CEIL) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_CEIL) //'-11'
b(11.5).toFixed(0, BigNumber.ROUND_FLOOR) //'11'
b(-11.5).toFixed(0, BigNumber.ROUND_FLOOR) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_UP) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_UP) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'11'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'-11'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'-12'
b(12.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'12'
b(-12.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'-12'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_CEIL) //'12'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_CEIL) //'-11'
b(11.5).toFixed(0, BigNumber.ROUND_HALF_FLOOR) //'11'
b(-11.5).toFixed(0, BigNumber.ROUND_HALF_FLOOR) //'-12'
调用 BigNumber.config(obj)
方法即可进行全局设置。但我本身不推荐使用该方法,因为可能会污染其他人的代码。为了保证结果的一致性,要么禁用该方法,要么在每次调用的时候手动传递参数。
总之, bignumber.js 是一个非常实用的库。压缩之后的体积 17.4kb 也非常轻量。使用它可以有效地规避语言本身的坑,让开发人员能够更好地专注于业务逻辑的实现,并且具有良好的语义化。
建议视为 underscore.js, moment.js 这样的工具库,在每一个项目中使用。
一个小题外话,65004 个测试用例,在 chrome 上要用 6s 的样子,在 ie9 上居然只用 1.3s 就完成了……