金融大数高精度计算解决方案

js 计算失误情况

​ 在用 js 进行数值计算的时候,比如0.1+0.2时,会产生精度问题。另一方面,number 的计算范围有限,当 number 用来计算整数时,其范围为-2^53 ~ 2^53,为 16 位数。当数字超过 16 位数时,js 既不会报错,会造成的影响只是计算结果不准确;另外,当数字是小于 1 且小数点后面带有 6 个 0 以上的浮点数值或者整数位数字多于 21 位,js 会自动将数字转为科学计数法。

​ 为了解决这种情况,我们可以使用以下方法:

​ 1. 输入数字的时候尽量限制范围,防止用户输入过大不合理的数字

  1. 如果有产生由用户输出数字计算出来的数字,要校验该数字是否超过数据库允许的范围。比如进行乘操作的时候。

  2. 数据库中,以分为单位进行存储,避免存储小数。

  3. 计算数据时,不要使用parseFloattoFixed相关 Number 对象方法进行计算,而是应该使用专门的库进行计算,比如此处说的 decimal.js 库

decimal.js

github: https://github.com/MikeMcl/decimal.js

decimal.js 是使用的二进制来计算的数字处理库,以下是基本运算:

1
2
3
4
5
6
7
8
9
10
11
// 加法
const c = new Decimal(a).plus(new Decimal(b));

// 减法
const d = new Decimal(a).sub(new Decimal(b));

// 乘法
const e = new Decimal(a).mul(new Decimal(b));

// 除法
const f = new Decimal(a).div(new Decimal(b));

另外,Decimal 有自己专门的toStringtoFixed方法。当然,decimal.js 还有很多其他的十分强大的功能。

DemCal

以下是根据 decimal.js 封装的一些进行计算时常用到的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const Decimal = require("./decimal.min.js");

/**
* 转换为Decimal类型
* @param {string/number} _num 传入的数字,如果在js的有效范围外,那么传入必须是整数,否则会发生数据小数位丢失问题
* @param {number} _fixedDecimal 需要保留的小数位,默认为0,即整数
* @param {number} _isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const toDecimal = (num, fixedDecimal = 0, isToStr = false) => {
/**
* 为什么不适用toString,或者String => 对于过大的数字,toString或者String会将其转换成科学计数法,比如1111111111111111111111111111111111111111会转变为"1.1111111111111112e+39"
*/
let _num = "" + num;
let _fixedDecimal = fixedDecimal;
_num = new Decimal(_num).toFixed(_fixedDecimal);
return isToStr ? _num.toString() : new Decimal(_num);
};
/**
* 两数相加
* @param {string/number} num1 传入的数字1
* @param {string/number} num2 传入的数字2
* @param {number} fixedDecimal 需要保留的小数位,默认为0, 此处四舍五入
* @param {number} isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const plus = (num1, num2, fixedDecimal = 0, isToStr = false) => {
const _num1 = toDecimal(num1);
const _num2 = toDecimal(num2);
const res = _num1.add(_num2).toFixed(fixedDecimal);
return isToStr ? res.toString() : new Decimal(res);
};

/**
* 两数相减
* @param {string/number} num1 传入的数字1
* @param {string/number} num2 传入的数字2
* @param {number} fixedDecimal 需要保留的小数位,此处四舍五入
* @param {number} isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const sub = (num1, num2, fixedDecimal = 0, isToStr = false) => {
const _num1 = toDecimal(num1);
const _num2 = toDecimal(num2);
const res = _num1.sub(_num2).toFixed(fixedDecimal);
return isToStr ? res.toString() : new Decimal(res);
};

/**
* 两数相除
* @param {string/number} _num1 传入的数字1
* @param {string/number} _num2 传入的数字2
* @param {number} _fixedDecimal 需要保留的小数位,此处四舍五入
* @param {number} isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const div = (num1, num2, fixedDecimal = 0, isToStr = false) => {
const _num1 = toDecimal(num1);
const _num2 = toDecimal(num2);
const res = _num1.div(_num2).toFixed(fixedDecimal);
return isToStr ? res.toString() : new Decimal(res);
};

/**
* 两数相乘
* @param {string/number} _num1 传入的数字1
* @param {string/number} _num2 传入的数字2
* @param {number} _fixedDecimal 需要保留的小数位,此处四舍五入
* @param {number} isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const mul = (num1, num2, fixedDecimal = 0, isToStr = false) => {
const _num1 = toDecimal(num1);
const _num2 = toDecimal(num2);
const res = _num1.mul(_num2).toFixed(fixedDecimal);
return isToStr ? res.toString() : new Decimal(res);
};

/**
* 累加
* @param {array} _numList 数字列表
* @param {string/number} _fixedDecimal 需要保留的小数位,此处四舍五入
* @param {string/number} isToStr 是否需要转换为string返回,默认返回Decimal类型
*/
const sum = (numList, fixedDecimal = 0, isToStr = false) => {
const res = numList.reduce((prev, next) => plus(prev, next));
return isToStr ? toDecimal(res, fixedDecimal, true) : toDecimal(res, fixedDecimal);
};

/**
* 用千分符分隔数字
* @param {string/number} num 数字
* @param {number} fixedDecimal 需要保留的小数位,此处四舍五入
* @returns {string} 返回带千分符分隔的字符串
*/
const numByThousandDelimiter = (num, fixedDecimal = 0) => {
const _num = toDecimal(num, fixedDecimal, true);
const decimalReg = /(\d)(?=(\d{3})+\.)/g;
const intReg = /(?=(?!\b)(\d{3})+$)/g;
return _num.indexOf(".") !== -1 ? _num.replace(decimalReg, "$1,") : _num.replace(intReg, ",");
};

const financeTool = {
toDecimal: toDecimal,
plus: plus,
sub: sub,
div: div,
mul: mul,
sum: sum,
numByThousandDelimiter: numByThousandDelimiter,
};

module.exports = financeTool;

使用

1
2
var financeTool = require("./financeTool");
console.log(financeTool.add(1.1, 4, 4, true));

参考

javascript 权威指南

decimal.js API

0%