[关闭]
@tangchao 2018-09-28T10:46:16.000000Z 字数 4413 阅读 510

bignumber.js

技术分享



js 存在的坑

浮点运算

编程语言大都有这样的一个问题,浮点运算,会出现问题:

  1. 0.1 + 0.2 //0.30000000000000004

甚至连语言本身的 Math 里面的函数也会存在问题:

  1. Math.pow(0.7, 2) //0.48999999999999994
  2. Math.sqrt(0.1156) //0.33999999999999997

比较

使用比较符号 >< 这些,如果有不是数字格式的情况,将会有自动转化,而且存在非常多不同的情况:

  1. '10.1' > 2.1 //true
  2. '10.1' > '2.1' //false

还有如 null, undefined, '' 在进行比较时,不报错,只显示 false:

  1. null >= 'a' //false
  2. null <= 'a' //false

这在数据来源于用户操作或者 ajax 请求时,特别容易出现错误。

四舍五入

在进行四舍五入的时候,和我们常规认识的“绝对值四舍五入再带上符号”不符合:

  1. Math.round(11.5) //12
  2. Math.round(-11.5) //-11

常见的解决方案

虽然存在这些语言本身的问题,但是测试不会管啊,产品不会管啊。任务不会变啊。java 中有一个 BigDecimal 类。而 js 本身不带有这个类,需要自己找一些库来实现。

网上主要有 mathjs, big.js, bignumber.js, decimal.js。后面三个是一位作者写的。

这几个库各有各的优势。我在这里主要介绍 bignumber.js。

bignumber.js

可以直接通过 <script> 标签引入 bignumber.js,也可以使用 AMD 或者 commonjs 模块规范引入。之后就可以用构造函数生成一个 BigNumber 的实例:

  1. var x = new BigNumber(123.4567)
  2. x.toString() //'123.4567'

这个实例本身是一个对象,记录着这个数的信息;所以,所有的功能,不是通过运算符号,而是通过方法去实现的。

同时,BigNumber 构造函数还拥有一些实用的静态方法。

运算

为了写法方便,先定义两个函数:

  1. function b(val) {
  2. return new BigNumber(val);
  3. }

小数相加:

  1. 0.1 + 0.2 //0.30000000000000004
  2. b(0.1).plus(0.2).toString() //'0.3'

小数相减:

  1. 0.3 - 0.2 //0.09999999999999998
  2. (0.3*10 - 0.2*10) / 10 //0.1
  3. b(0.3).minus(0.2).toString() //'0.1'

除法:

  1. 355 / 113 //3.1415929203539825
  2. b(355).div(113).toString() //'3.14159292035398230088'

乘法:

  1. 0.6 * 3 //1.7999999999999998
  2. b(0.6).times(3).toString() //'1.8'

取模:

  1. 1 % 0.9 //0.09999999999999998
  2. b(1).mod(0.9).toString() //'0.1'

幂:

  1. Math.pow(0.7, 2) //0.48999999999999994
  2. b(0.7).pow(2).toString() //'0.49'

平方根:

  1. Math.sqrt(0.1156) //0.33999999999999997
  2. b(0.1156).squareRoot().toString() //'0.34'

调整

调整小数位数,如果不传参,则给 get 方法,获取小数位数

  1. b('314.15929203539825').dp(2).toString() //'314.16'
  2. b('314.15929203539825').dp() //14

调整精度,如果不传参,则给 get 方法,获取小数位数:

  1. b('314.15929203539825').sd(2).toString() //'310'
  2. b('314.15929203539825').sd() //17

小数位数和精度是比较容易弄混的。一个是小数点后的数字保留几位,一个是所有数字保留几位。

调整小数点位置:

  1. b('314.15929203539825').shiftedBy(-2).toString() //'3.1415929203539825'

取反:

  1. b('314.15929203539825').negated().toString() //'-314.15929203539825'

比较和判断

比较主要是:gt, gte, lt, lte, eq 方法,除此之外还有 comparedTo

  1. '10.1' > 2.1 //true
  2. '10.1' > '2.1' //false
  3. b('10.1').gt('2.1') //true

其余的以 is 开头的方法,大都是用来判断的。比如:

  1. b(-0).isPositive() //false
  2. b(0).isPositive() //true

输出

上面的计算和调整类方法,都是返回一个 BigNumber 的实例,用于下一步的操作。所以可以进行链式调用。

我想要得到一个值,可以通过输出方法来输出一个想要的值。to 开头的方法和 valueOf 就是相应的输出方法。

最常用:toString(),接受进制,默认为 10 进制。然后输出其值。

  1. b('3141.5929203539825').toString() //'3141.5929203539825'

toFixed(),接收一个参数,保留的小数位数。

  1. b('3141.5929203539825').toFixed(2) //'3141.59'

toFormat(),接收一个参数,保留的小数位数,转化数字格式化的字符串。

  1. 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 的第二个参数中,不填则取默认值。

在调用的时候,既可以写常量属性名,也可以写数字,但是建议写常量属性,这样更具有可读性。

  1. Math.ceil(11.5) //12
  2. Math.ceil(-11.5) //-11
  3. Math.floor(11.5) //11
  4. Math.floor(-11.5) //-12
  5. Math.round(11.5) //12
  6. Math.round(-11.5) //-11
  7. parseFloat(11.5).toFixed(0) //'12'
  8. parseFloat(-11.5).toFixed(0) //'-12'
  9. b(11.5).toFixed(0, BigNumber.ROUND_UP) //'12'
  10. b(-11.5).toFixed(0, BigNumber.ROUND_UP) //'-12'
  11. b(11.5).toFixed(0, BigNumber.ROUND_DOWN) //'11'
  12. b(-11.5).toFixed(0, BigNumber.ROUND_DOWN) //'-11'
  13. b(11.5).toFixed(0, BigNumber.ROUND_CEIL) //'12'
  14. b(-11.5).toFixed(0, BigNumber.ROUND_CEIL) //'-11'
  15. b(11.5).toFixed(0, BigNumber.ROUND_FLOOR) //'11'
  16. b(-11.5).toFixed(0, BigNumber.ROUND_FLOOR) //'-12'
  17. b(11.5).toFixed(0, BigNumber.ROUND_HALF_UP) //'12'
  18. b(-11.5).toFixed(0, BigNumber.ROUND_HALF_UP) //'-12'
  19. b(11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'11'
  20. b(-11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'-11'
  21. b(11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'12'
  22. b(-11.5).toFixed(0, BigNumber.ROUND_HALF_DOWN) //'-12'
  23. b(11.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'12'
  24. b(-11.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'-12'
  25. b(12.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'12'
  26. b(-12.5).toFixed(0, BigNumber.ROUND_HALF_EVEN) //'-12'
  27. b(11.5).toFixed(0, BigNumber.ROUND_HALF_CEIL) //'12'
  28. b(-11.5).toFixed(0, BigNumber.ROUND_HALF_CEIL) //'-11'
  29. b(11.5).toFixed(0, BigNumber.ROUND_HALF_FLOOR) //'11'
  30. 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 就完成了……

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注