Decimal.js 原理
JavaScript 中的数字类型是基于 IEEE 754 标准来实现的,该标准通常也被称为“浮点数”。大部分十进制转二进制时是无限的,而计算机有限的空间无法存储无限的小数,只能存储一部分,丢失的那部分就造成了微差。
在计算如 0.1 + 0.2 结果是 0.30000000000000004 而非 0.3,这种错误并非 ECMAScript 所独有。其他使用相同格式的语言也有这个问题。
不同于“浮点数”的二进制存储机制,decimal.js 使用自定义的数字存储格式,Decimal实例通过三个属性精确表示十进制数:
d: 有效数字数组,存储十进制数的每一位数字
e: 指数部分
s: 符号部分, 1 表示正数, -1 表示负数
使用示例
1 2 3 4 5 6 7 8 9 const Decimal = require ("./decimal.js" );console .log (0.1 + 0.2 ); const a = new Decimal (0.1 );const b = new Decimal (0.2 );console .log (a.add (b).toString ());
Decimal入口实现
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 ;(function (globalScope ) { 'use strict' ; ... Decimal = clone (DEFAULTS ); Decimal .prototype .constructor = Decimal ; Decimal ['default' ] = Decimal .Decimal = Decimal ; ... if (typeof define == 'function' && define.amd ) { define (function ( ) { return Decimal ; }); } else if (typeof module != 'undefined' && module .exports ) { if (typeof Symbol == 'function' && typeof Symbol .iterator == 'symbol' ) { P[Symbol ['for' ]('nodejs.util.inspect.custom' )] = P.toString ; P[Symbol .toStringTag ] = 'Decimal' ; } module .exports = Decimal ; } else { if (!globalScope) { globalScope = typeof self != 'undefined' && self && self.self == self ? self : window ; } noConflict = globalScope.Decimal ; Decimal .noConflict = function ( ) { globalScope.Decimal = noConflict; return Decimal ; }; globalScope.Decimal = Decimal ; } })(this );
DEFAULTS 默认配置
DEFAULTS 对象存储了 Decimal 构造函数的初始配置属性。这些属性定义了该库进行数值计算时的全局默认行为。
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 // Decimal 构造函数的初始配置属性 DEFAULTS = { // 这些值必须是指定范围(包含边界值)内的整数. // 大多数值可以在运行时通过 `Decimal.config` 方法进行更改. // 计算或进制转换结果的最大有效数字位数。默认值为 20。 precision: 20, // 1 到 MAX_DIGITS // 舍入到指定 `precision`(精度)时使用的舍入模式. // // ROUND_UP 0 远离零 // ROUND_DOWN 1 趋向零 // ROUND_CEIL 2 趋向 +∞ // ROUND_FLOOR 3 趋向 -∞ // ROUND_HALF_UP 4 四舍五入,.5 向上 // ROUND_HALF_DOWN 5 四舍五入,.5 向下 // ROUND_HALF_EVEN 6 四舍五入,.5 向偶数 // ROUND_HALF_CEIL 7 四舍五入,.5 向 +∞ // ROUND_HALF_FLOOR 8 四舍五入,.5 向 -∞ // 舍入模式.当结果需要截断到 precision 指定的精度时使用四舍五入,.5 向上舍入模式 // `Decimal.rounding = 4;` // `Decimal.rounding = Decimal.ROUND_HALF_UP;` rounding: 4, // 0 to 8 // 当计算模运算(a mod n)时使用的舍入模式。 // 商(q = a / n)根据对应的舍入模式进行计算。 // 余数(r)根据公式计算: r = a - n * q. // // UP 0 如果被除数为负数,则余数为正;否则为负数. // DOWN 1 余数与被除数符号相同 (JavaScript %) . // FLOOR 3 余数与除数符号相同 (Python %) . // HALF_EVEN 6 IEEE 754 取余函数. // EUCLID 9 欧几里得除法。q = sign(n) * floor(a / abs(n) )。始终为正. // modulo: 1, // 0 to 9 // `toString` 返回指数表示法的最小指数值 toExpNeg: -7, // 0 to -EXP_LIMIT // `toString` 返回指数表示法的指数值阈值 toExpPos: 21, // 0 to EXP_LIMIT // 最小指数值,低于此值将发生下溢至零 // JavaScript numbers: -324 (5e-324) minE: -EXP_LIMIT, // -1 to -EXP_LIMIT // 最大指数值,高于此值将发生溢出至无穷大 // JavaScript numbers: 308 (1.7976931348623157e+308) maxE: EXP_LIMIT, // 1 to EXP_LIMIT // 是否使用加密安全的随机数生成器(如果可用) crypto: false // true /false },
rounding 舍入模式
值
名称
描述
对正数(1.5)
对负数(-1.5)
对正数(1.4)
对负数(-1.4)
0
ROUND_UP
远离零
2
-2
2
-2
1
ROUND_DOWN
趋向零
1
-1
1
-1
2
ROUND_CEIL
趋向 +∞
2
-1
2
-1
3
ROUND_FLOOR
趋向 -∞
1
-2
1
-2
4
ROUND_HALF_UP
四舍五入,.5 向上
2
-2
1
-1
5
ROUND_HALF_DOWN
四舍五入,.5 向下
1
-1
1
-1
6
ROUND_HALF_EVEN
四舍五入,.5 向偶数
2
-2
1
-1
7
ROUND_HALF_CEIL
四舍五入,.5 向 +∞
2
-1
1
-1
8
ROUND_HALF_FLOOR
四舍五入,.5 向 -∞
1
-2
1
-1
clone函数实现
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 function clone(obj) { var i, p, ps; function Decimal(v) { ... } tag = '[object Decimal]', P = { toStringTag: tag }; Decimal.prototype = P; Decimal.ROUND_UP = 0 ; Decimal.ROUND_DOWN = 1 ; Decimal.ROUND_CEIL = 2 ; Decimal.ROUND_FLOOR = 3 ; Decimal.ROUND_HALF_UP = 4 ; Decimal.ROUND_HALF_DOWN = 5 ; Decimal.ROUND_HALF_EVEN = 6 ; Decimal.ROUND_HALF_CEIL = 7 ; Decimal.ROUND_HALF_FLOOR = 8 ; Decimal.EUCLID = 9 ; Decimal.config = Decimal.set = config; Decimal.clone = clone; Decimal.isDecimal = isDecimalInstance; Decimal.abs = abs ; Decimal.acos = acos ; Decimal.acosh = acosh ; Decimal.add = add; Decimal.asin = asin ; Decimal.asinh = asinh ; Decimal.atan = atan ; Decimal.atanh = atanh ; Decimal.atan2 = atan2; Decimal.cbrt = cbrt; Decimal.ceil = ceil ; Decimal.clamp = clamp ; Decimal.cos = cos ; Decimal.cosh = cosh ; Decimal.div = div; Decimal.exp = exp ; Decimal.floor = floor ; Decimal.hypot = hypot; Decimal.ln = ln; Decimal.log = log ; Decimal.log10 = log10; Decimal.log2 = log2 ; Decimal.max = max ; Decimal.min = min ; Decimal.mod = mod ; Decimal.mul = mul; Decimal.pow = pow ; Decimal.random = random; Decimal.round = round ; Decimal.sign = sign ; Decimal.sin = sin ; Decimal.sinh = sinh ; Decimal.sqrt = sqrt ; Decimal.sub = sub; Decimal.sum = sum; Decimal.tan = tan ; Decimal.tanh = tanh ; Decimal.trunc = trunc ; if (obj === void 0 ) obj = {}; if (obj) { if (obj.defaults !== true ) { ps = ['precision ', 'rounding', 'toExpNeg', 'toExpPos', 'maxE', 'minE', 'modulo', 'crypto']; for (i = 0 ; i < ps.length ;) if (!obj.hasOwnProperty(p = ps[i++])) obj[p] = this[p]; } } Decimal.config(obj); return Decimal; }
Decimal构造函数实现
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 function Decimal(v) { var e, i, t, x = this ; if (!(x instanceof Decimal)) return new Decimal(v); x.constructor = Decimal; if (isDecimalInstance(v)) { x.s = v.s; if (external ) { if (!v.d || v.e > Decimal.maxE) { x.e = NaN; x.d = null ; } else if (v.e < Decimal.minE) { x.e = 0 ; x.d = [0 ]; } else { x.e = v.e; x.d = v.d.slice(); } } else { x.e = v.e; x.d = v.d ? v.d.slice() : v.d; } return ; } t = typeof v; if (t === "number" ) { if (v === 0 ) { x.s = 1 / v < 0 ? -1 : 1 ; x.e = 0 ; x.d = [0 ]; return ; } if (v < 0 ) { v = -v; x.s = -1 ; } else { x.s = 1 ; } if (v === ~~v && v < 1e7 ) { for (e = 0 , i = v; i >= 10 ; i /= 10 ) e++; if (external ) { if (e > Decimal.maxE) { x.e = NaN; x.d = null ; } else if (e < Decimal.minE) { x.e = 0 ; x.d = [0 ]; } else { x.e = e; x.d = [v]; } } else { x.e = e; x.d = [v]; } return ; } if (v * 0 !== 0 ) { if (!v) x.s = NaN; x.e = NaN; x.d = null ; return ; } return parseDecimal(x, v.toString()); } if (t === "string" ) { if ((i = v.charCodeAt(0 )) === 45 ) { v = v.slice(1 ); x.s = -1 ; } else { if (i === 43 ) v = v.slice(1 ); x.s = 1 ; } return isDecimal.test(v) ? parseDecimal(x, v) : parseOther(x, v); } if (t === "bigint" ) { if (v < 0 ) { v = -v; x.s = -1 ; } else { x.s = 1 ; } return parseDecimal(x, v.toString()); } throw Error(invalidArgument + v); }
parseDecimal 函数实现
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 function parseDecimal (x, str ) { var e, i, len; if ((e = str.indexOf ('.' )) > -1 ) str = str.replace ('.' , '' ); if ((i = str.search(/e/i )) > 0 ) { if (e < 0 ) e = i; e += +str.slice (i + 1 ); str = str.substring(0 , i); } else if (e < 0 ) { e = str.length ; } for (i = 0 ; str.charCodeAt(i) === 48 ; i++); for (len = str.length ; str.charCodeAt(len - 1 ) === 48 ; --len); str = str.slice (i, len); if (str) { len -= i; x.e = e = e - i - 1 ; x.d = []; i = (e + 1 ) % LOG_BASE; if (e < 0 ) i += LOG_BASE; if (i < len) { if (i) x.d.push (+str.slice (0 , i)); for (len -= LOG_BASE; i < len;) x.d.push (+str.slice (i, i += LOG_BASE)); str = str.slice (i); i = LOG_BASE - str.length ; } else { i -= len; } for (; i--;) str += '0' ; x.d.push (+str); if (external) { if (x.e > x.constructor.maxE) { x.d = null ; x.e = NaN ; } else if (x.e < x.constructor.minE) { x.e = 0 ; x.d = [0 ]; } } } else { x.e = 0 ; x.d = [0 ]; } return x; }
add / plus 函数实现
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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 P.plus = P.add = function (y ) { var carry, d, e, i, k, len, pr, rm, xd, yd, x = this, Ctor = x .constructor ; y = new Ctor(y ); if (!x .d || !y .d) { if (!x .s || !y .s) y = new Ctor(NaN ); else if (!x .d) y = new Ctor(y .d || x .s === y .s ? x : NaN ); return y ; } if (x .s != y .s) { y .s = -y .s; return x .minus(y ); } xd = x .d; yd = y .d; pr = Ctor.precision; rm = Ctor.rounding; if (!xd[0 ] || !yd[0 ]) { if (!yd[0 ]) y = new Ctor(x ); return external ? finalise(y , pr, rm) : y ; } k = mathfloor(x .e / LOG_BASE); e = mathfloor(y .e / LOG_BASE); xd = xd.slice(); i = k - e; if (i) { if (i < 0 ) { d = xd; i = -i; len = yd.length; } else { d = yd; e = k; len = xd.length; } k = Math.ceil (pr / LOG_BASE); len = k > len ? k + 1 : len + 1 ; if (i > len) { i = len; d.length = 1 ; } d.reverse(); for (; i--;) d.push(0 ); d.reverse(); } len = xd.length; i = yd.length; if (len - i < 0 ) { i = len; d = yd; yd = xd; xd = d; } for (carry = 0 ; i;) { carry = (xd[--i] = xd[i] + yd[i] + carry) / BASE | 0 ; xd[i] %= BASE; } if (carry) { xd.unshift(carry); ++e; } for (len = xd.length; xd[--len] == 0 ;) xd.pop(); y .d = xd; y .e = getBase10Exponent(xd, e); return external ? finalise(y , pr, rm) : y ; };