precise-calculator
Version:
Financial precise calculator
1,022 lines (956 loc) • 23.2 kB
JavaScript
const inspect = require('util').inspect
const { upRound, evenRound, format } = require('./utils')
const _pow = Math.pow
const _ceil = Math.ceil
const _floor = Math.floor
const _round = Math.round
const _abs = Math.abs
/**
* 计算器
*/
class Calculator {
/**
* @param {Number|String|Calculator} v 初始化值
*/
constructor(v) {
// this._p 小数位数,大于0表示小数,小于0表示10的n次方
// this._vi 去掉小数点的数值
// this._v 缓存真实值,计算过程中置为null
if (v) {
if (v._vi === undefined) {
const s = v.toString()
const pe = s.lastIndexOf('e')
if (pe > 0) { // 科学计数法
this._p = 0 - Number(s.slice(pe + 1))
const p = s.indexOf('.')
if (p < 0) {
this._vi = Number(s.slice(0, pe))
} else {
this._p += pe - p - 1
if (this._p < 0) {
this._vi = Number(s) * _pow(10, this._p)
} else {
this._vi = Number(s.slice(0, p) + s.slice(p + 1, pe))
}
}
this._v = null
} else {
const p = s.indexOf('.')
if (p < 0) {
this._vi = this._v = Number(v)
this._p = 0
} else {
this._vi = Number(s.substr(0, p) + s.substr(p + 1))
this._p = s.length - p - 1
this._v = null
}
}
} else {
this._vi = v._vi
this._p = v._p
this._v = v._v || null
}
} else {
this._vi = 0
this._p = 0
this._v = null
}
}
/**
* 普通加法`+`
* @param {Number|String} v 加数
* @returns {Calculator} this
*/
add (v) {
if (v) {
const _s = v.toString()
const _pe = _s.lastIndexOf('e')
let _p = 0
let _vi = 0
if (_pe > 0) { // 科学计数法
_p = 0 - Number(_s.slice(_pe + 1))
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(_s.slice(0, _pe))
} else {
_p += _pe - p - 1
if (_p < 0) {
_vi = Number(_s) * _pow(10, _p)
} else {
_vi = Number(_s.slice(0, p) + _s.slice(p + 1, _pe))
}
}
} else {
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(v)
_p = 0
} else {
_vi = Number(_s.substr(0, p) + _s.substr(p + 1))
_p = _s.length - p - 1
}
}
if (_p > this._p) {
this._vi = this._vi * _pow(10, _p - this._p) + _vi
this._p = _p
} else {
this._vi = this._vi + _vi * _pow(10, this._p - _p)
}
this._v = null
}
return this
}
/**
* 复合加法`+`
* @param {Calculator} v 表达式加数
*/
$add (v) {
if (v._p > this._p) {
this._vi = this._vi * _pow(10, v._p - this._p) + v._vi
this._p = v._p
} else {
this._vi = this._vi + v._vi * _pow(10, this._p - v._p)
}
this._v = null
return this
}
/**
* 普通减法`-`
* @param {Number|String} v 减数
*/
sub (v) {
if (v) {
const _s = v.toString()
const _pe = _s.lastIndexOf('e')
let _p = 0
let _vi = 0
if (_pe > 0) { // 科学计数法
_p = 0 - Number(_s.slice(_pe + 1))
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(_s.slice(0, _pe))
} else {
_p += _pe - p - 1
if (_p < 0) {
_vi = Number(_s) * _pow(10, _p)
} else {
_vi = Number(_s.slice(0, p) + _s.slice(p + 1, _pe))
}
}
} else {
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(v)
_p = 0
} else {
_vi = Number(_s.substr(0, p) + _s.substr(p + 1))
_p = _s.length - p - 1
}
}
if (_p > this._p) {
this._vi = this._vi * _pow(10, _p - this._p) - _vi
this._p = _p
} else {
this._vi = this._vi - _vi * _pow(10, this._p - _p)
}
this._v = null
}
return this
}
/**
* 复合减法`-`
* @param {Calculator} v 复合减数
* @returns {Calculator} this
*/
$sub (v) {
if (v._p > this._p) {
this._vi = this._vi * _pow(10, v._p - this._p) - v._vi
this._p = v._p
} else {
this._vi = this._vi - v._vi * _pow(10, this._p - v._p)
}
this._v = null
return this
}
/**
* 普通乘法`*`
* @param {Number|String} v 乘数
* @returns {Calculator} this
*/
mul (v) {
if (v) {
const _s = v.toString()
const _pe = _s.lastIndexOf('e')
let _p = 0
let _vi = 0
if (_pe > 0) { // 科学计数法
_p = 0 - Number(_s.slice(_pe + 1))
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(_s.slice(0, _pe))
} else {
_p += _pe - p - 1
if (_p < 0) {
_vi = Number(_s) * _pow(10, _p)
} else {
_vi = Number(_s.slice(0, p) + _s.slice(p + 1, _pe))
}
}
} else {
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(v)
_p = 0
} else {
_vi = Number(_s.substr(0, p) + _s.substr(p + 1))
_p = _s.length - p - 1
}
}
if (this._p === 0 && _p > 0) {
let s = this._vi.toString();
for (let i = s.length - 1; i >= 0; i--) {
if (_p > 0 && s[i] === '0') {
--_p;
} else {
this._vi = Number(s.substr(0, i + 1));
break;
}
}
this._vi = this._vi * _vi
this._p = _p
} else {
this._vi = this._vi * _vi
this._p = this._p + _p
}
} else {
this._vi = 0
this._p = 0
}
this._v = null
return this
}
/**
* 复合乘法`*`
* @param {Calculator} v 复合乘数
* @returns {Calculator} this
*/
$mul (v) {
this._p = this._p + v._p
if (this._p > 16) {
this._vi = _round(this._vi * v._vi / _pow(10, this._p - 16))
this._p = 16
} else {
this._vi = this._vi * v._vi
}
this._v = null
return this
}
/**
* 普通除法`/`
* @param {Number|String} v 除数
* @returns {Calculator} this
*/
div (v) {
if (v) {
const _s = v.toString()
const _pe = _s.lastIndexOf('e')
let _p = 0
let _vi = 0
if (_pe > 0) { // 科学计数法
_p = 0 - Number(_s.slice(_pe + 1))
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(_s.slice(0, _pe))
} else {
_p += _pe - p - 1
if (_p < 0) {
_vi = Number(_s) * _pow(10, _p)
} else {
_vi = Number(_s.slice(0, p) + _s.slice(p + 1, _pe))
}
}
} else {
const p = _s.indexOf('.')
if (p < 0) {
_vi = Number(v)
_p = 0
} else {
_vi = Number(_s.substr(0, p) + _s.substr(p + 1))
_p = _s.length - p - 1
}
}
this._vi = this._vi / _vi
let s = this._vi.toString()
let pe = s.indexOf('e') // 除完之后是否有科学计数
if (pe < 0) {
const pos = s.indexOf('.')
if (pos > 0) {
this._vi = Number(s.substr(0, pos) + s.substr(pos + 1))
this._p = this._p - _p + s.length - pos - 1
} else {
this._p -= _p
}
} else {
const e = Number(s.substr(pe + 1))
const pos = s.indexOf('.')
// assert.ok(pos > 0, '不带0整数相除不会产生没有小数的科学计数')
// if (pos < 0) {
// this._vi = Number(s.substr(0, pe))
// this._p = this._p - _p - e
// }
this._vi = Number(s.substr(0, pos) + s.substring(pos + 1, pe))
this._p = this._p - _p + pe - pos - 1 - e
}
} else {
this._vi = Infinity
this._p = 0
}
this._v = null
return this
}
/**
* 复合除法`/`
* @param {Calculator} v 复合除数
* @returns {Calculator} this
*/
$div (v) {
this._vi = this._vi / v._vi
let s = this._vi.toString()
const pe = s.indexOf('e')
if (pe < 0) {
let pos = s.indexOf('.')
if (pos < 0) {
this._p = this._p - v._p
} else {
// // 去掉小数末尾的0
// let i = s.length - 1;
// while (i > pos && s[i] === '0') i--;
// i++;
this._vi = Number(s.substr(0, pos) + s.substr(pos + 1))
this._p = this._p - v._p + s.length - pos - 1
}
} else {
const e = Number(s.substr(pe + 1))
const pos = s.indexOf('.')
// assert.ok(pos > 0, '不带0整数相除不会产生没有小数的科学计数')
// if (pos < 0) {
// this._p = this._p - v._p - e
// }
this._vi = Number(s.substr(0, pos) + s.substring(pos + 1, pe))
this._p = this._p - v._p + pe - pos - 1 - e
}
this._v = null
return this
}
max (v) {
const s = v.toString()
const pos = s.indexOf('.')
let _p = 0
let _vi
if (pos < 0) {
_vi = Number(v)
} else {
_vi = Number(s.substr(0, pos) + s.substr(pos + 1))
_p = s.length - pos - 1
}
if (this._p > _p) {
let _di = _vi * _pow(10, this._p - _p)
if (_di > this._vi) {
this._p = _p
this._vi = _vi
this._v = null
}
} else {
let _di = this._vi * _pow(10, _p - this._p)
if (_vi > _di) {
this._p = _p
this._vi = _vi
this._v = null
}
}
return this;
}
$max (v) {
if (this._p > v._p) {
let _di = v._vi * _pow(10, this._p - v._p)
if (_di > this._vi) {
return v
}
} else {
let _di = this._vi * _pow(10, v._p - this._p)
if (v._vi > _di) {
return v
}
}
return this
}
min (v) {
const s = v.toString()
const pos = s.indexOf('.')
let _p = 0
let _vi
if (pos < 0) {
_vi = Number(v)
} else {
_vi = Number(s.substr(0, pos) + s.substr(pos + 1))
_p = s.length - pos - 1
}
if (this._p > _p) {
let _di = _vi * _pow(10, this._p - _p)
if (_di < this._vi) {
this._p = _p
this._vi = _vi
this._v = null
}
} else {
let _di = this._vi * _pow(10, _p - this._p)
if (_vi < _di) {
this._p = _p
this._vi = _vi
this._v = null
}
}
return this;
}
$min (v) {
if (this._p > v._p) {
let _di = v._vi * _pow(10, this._p - v._p)
if (_di < this._vi) {
return v
}
} else {
let _di = this._vi * _pow(10, v._p - this._p)
if (v._vi < _di) {
return v
}
}
return this
}
/**
* @returns {Number} 当前计算结果
*/
v () {
if (this._v === null) {
if (this._p === 0) {
this._v = this._vi
} else {
let s = this.vs();
this._v = Number(s);
}
}
return this._v
}
/**
* 返回值
*/
value () {
return this.v();
}
vs () {
let s = this._vi.toString()
let sign = ''
if (this._vi < 0) {
sign = '-'
s = s.substr(1)
}
if (this._p > 0) { // 小数
let pe = s.indexOf('e')
if (pe > 0) {
let e = Number(s.substr(pe + 1)) - this._p;
let p = s.indexOf('.');
if (p > 0) { // 2.12e+21
if (e < 0) {
s = s.substring(0, p) + s.substring(p + 1, pe)
while ((++e) < 0) {
s = '0' + s
}
return sign + '0.' + s
}
let n = e - (pe - p - 1)
if (n < 0) {
return sign + s.substring(0, p) + s.substr(p + 1, e) + '.' + s.substring(p + 1 + e, pe);
}
s = s.substring(0, p) + s.substring(p + 1, pe)
while ((n--) > 0) {
s += '0';
}
return sign + s
} else { // 2e+21 or 2e-8
if (e > 0) {
s = s.substr(0, pe)
while ((e--) > 0) {
s += '0'
}
return sign + s;
}
s = s.substr(0, pe)
while ((++e) < 0) {
s = '0' + s
}
return sign + '0.' + s
}
} else {
let p = this._p - s.length
if (p < 0) {
p = 0 - p
return sign + s.substring(0, p) + '.' + s.substring(p)
} else {
while ((p--) > 0) {
s = '0' + s
}
return sign + '0.' + s
}
}
} else if (this._p < 0) { // 整数
let pe = s.indexOf('e')
if (pe > 0) {
let p = s.indexOf('.')
let e = Number(s.substr(pe + 1)) - this._p
if (p > 0) { // 2.12e+21
let n = e - (pe - p - 1)
if (n < 0) {
return sign + s.substring(0, p) + s.substr(p + 1, e) + '.' + s.substring(p + 1 + e, pe);
}
s = s.substring(0, p) + s.substring(p + 1, pe)
while ((n--) > 0) {
s += '0'
}
return sign + s
} else { // 2e+21
s = s.substr(0, pe)
while ((e--) > 0) {
s += '0'
}
return sign + s
}
} else {
let p = 0 - this._p
while ((p--) > 0) {
s += '0'
}
return sign + s
}
}
return sign + s
}
ve () {
if (this._vi === 0) {
return this._vi.toString()
} else {
let s = this._vi.toString()
if (s.indexOf('e') > 0) {
return s;
}
let sign = ''
if (this._vi < 0) {
sign = '-'
s = s.substr(1)
}
let i = s.length - 1
let p = (i - this._p).toString()
while (i > 0 && s[i] === '0') i--;
i++;
const sp = s[0] === '-' ? 2 : 1
if (i > sp) {
if (p[0] === '-') {
return sign + s.substr(0, sp) + '.' + s.substr(sp) + 'e' + p
} else {
return sign + s.substr(0, sp) + '.' + s.substr(sp) + 'e+' + p
}
} else {
if (p[0] === '-') {
return sign + s.substr(0, sp) + 'e' + p
} else {
return sign + s.substr(0, sp) + 'e+' + p
}
}
}
}
/**
* 按照当前舍入方式(默认四舍五入,可以通过`setup`设置默认的舍入方式)返回指定精度的结果
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
round (precision = 0) {
return upRound(this._vi, this._p, precision)
}
/**
* `round`别名
* @see this.round
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
rv (precision = 0) {
return this.round(precision)
}
/**
* 按照四舍五入返回指定精度的结果
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
upRound (precision = 0) {
return upRound(this._vi, this._p, precision)
}
/**
* `upRound`别名
* @see this.upRound
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
uv (precision = 0) {
return this.upRound(precision)
}
/**
* 按照银行家算法返回指定精度的结果
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
evenRound (precision = 0) {
return evenRound(this._vi, this._p, precision)
}
/**
* `evenRound`别名
* @see this.evenRound
* @param {Number} precision 精度
* @returns {Number} 计算结果
*/
ev (precision = 0) {
return this.evenRound(precision)
}
/**
* 按照当前舍入方式(默认四舍五入)对当前计算结果进行舍入操作
* @param {Number} precision 精度
* @returns {Calculator} this
*/
r (precision = 0) {
return this.ru(precision)
}
/**
* 按照四舍五入对当前计算结果进行舍入操作
* @param {Number} precision 精度
* @returns {Calculator} this
*/
ru (precision = 0) {
if (this._p > precision) {
let s = this._vi.toString()
let sign = 1
if (this._vi < 0) {
sign = -1
s = s.substr(1)
}
let p = s.length - this._p + precision // 需要移动的位数
if (p < 0) {
this._vi = 0
this._p = 0
} else {
this._vi = sign * (Number(s.substr(0, p)) + _round(Number('0.' + s.substr(p))))
this._p = precision
}
this._v = null
}
return this
}
/**
* 按照银行家算法对当前计算结果进行舍入操作
* @param {Number} precision 精度
* @returns {Calculator} this
*/
re (precision = 0) {
if (this._p > precision) {
let s = this._vi.toString()
let sign = 1
if (this._vi < 0) {
sign = -1
s = s.substr(1)
}
let p = s.length - this._p + precision // 需要移动的位数
if (p < 0) {
this._vi = 0
this._p = 0
} else {
this._vi = sign * evenRound(Number(s.substr(0, p) + '.' + s.substr(p)), 0, 0)
this._p = precision
}
this._v = null
}
return this
}
/**
* 对当前计算结果进行向上舍入
* @param {Number} precision 精度
* @returns {Calculator} this
*/
rc (precision = 0) {
if (this._p > precision) {
let s = this._vi.toString()
let sign = 1
if (this._vi < 0) {
sign = -1
s = s.substr(1)
}
let p = s.length - this._p + precision // 需要移动的位数
if (p < 0) {
if (this._vi > 0) {
this._vi = sign
this._p = precision
} else {
this._vi = 0
this._p = 0
}
} else {
this._vi = sign * (Number(s.substr(0, p)) + _ceil(Number('0.' + s.substr(p))))
this._p = precision
}
this._v = null
}
return this
}
/**
* 对当前计算结果进行向下舍入
* @param {Number} precision 精度
* @returns {Calculator} this
*/
rf (precision = 0) {
if (this._p > precision) {
let s = this._vi.toString()
let sign = 1
if (this._vi < 0) {
sign = -1
s = s.substr(1)
}
let p = s.length - this._p + precision // 需要移动的位数
if (p < 0) {
this._vi = 0
this._p = 0
} else {
this._vi = sign * Number(s.substr(0, p))
this._p = precision
}
this._v = null
}
return this
}
/**
* 向上舍入
* @param {Number} precision 精度
* @returns {Number} 结果
*/
ceil (precision = 0) {
if (this._p > precision) {
const m = _pow(10, this._p - precision)
return _ceil(this._vi / m) / _pow(10, precision)
} else if (this._p < 0) {
return this._vi * _pow(10, -this._p)
} else {
return this._vi / _pow(10, this._p)
}
}
/**
* 向上舍入
* @param {Number} precision 精度
* @returns {Number} 结果
*/
cv (precision = 0) {
return this.ceil(precision)
}
/**
* 向下舍入
* @param {Number} precision 精度
* @returns {Number} 结果
*/
floor (precision = 0) {
if (this._p > precision) {
const m = _pow(10, this._p - precision)
return _floor(this._vi / m) / _pow(10, precision)
} else {
return this._vi / _pow(10, this._p)
}
}
/**
* 向下舍入
* @param {Number} precision 精度
* @returns {Number} 结果
*/
fv (precision = 0) {
return this.floor(precision)
}
fmt (fmt = '#,##0.00', prefix = '', suffix = '') {
return this.format(fmt, prefix, suffix)
}
format (fmt = '#,##0.00', prefix = '', suffix = '') {
return format(this.v(), fmt, prefix, suffix)
}
thousands (precision = 2) {
return format(this.rv(precision), '#,##0.', '', '', ',', precision);
}
/**
* 按照货币格式化
* @param {*} currency 货币,可以为空
* @param {*} positiveSign
*/
currency (currency = '', positiveSign = false) {
currency = currency || '';
if (positiveSign && this._vi >= 0) {
currency = currency + '+';
}
return format(this.rv(2), '#,##0.00', currency, '');
}
/**
* 格式化成有符号
* @param {*} prefix 前缀
* @param {*} [precision=2] 精度(默认2)
* @returns {String} 格式化结果
*/
signed (prefix = '', precision = 2) {
prefix = prefix || '';
if (this._vi >= 0) {
prefix = prefix + '+';
}
return format(this.rv(precision), '0.00', prefix, '', '', precision);
}
/**
* 格式化成无符号
* @param {*} prefix 前缀
* @param {*} [precision=2] 精度(默认2)
* @returns {String} 格式化结果
*/
unsigned (prefix = '', precision = 2) {
prefix = prefix || '';
let v = this.rv(precision);
if (this._vi < 0) {
v = v.toString().slice(1);
}
return format(v, '0.', prefix, '', '', precision);
}
debug () {
this._print(this._vi, this._p)
return this
}
/**
* 重载直接用于运算符
*/
valueOf () {
return this.v()
}
/**
* 重载返回值
*/
toString () {
return this.vs()
}
/**
* 重载用于JSON.stringify
*/
toJSON () {
return this.v()
}
/**
* 重载用于inspect以及console`console.log($C(1))`
*/
[inspect.custom] () {
return this.v()
}
/**
* 是否为0
*/
isZero () {
return this._vi === 0
}
/**
* 正数
*/
positive () {
return this._vi > 0
}
/**
* 负数
*/
negative () {
return this._vi < 0
}
/**
* 取绝对值
*/
abs () {
this._vi = _abs(this._vi)
this._v = null
return this;
}
}
class StrictCalculator extends Calculator {
constructor(v) {
super(v)
if (!(v && v._vi)) {
this.check(v)
}
}
check (v) {
if (isNaN(v)) throw new Error(`Invalid number[${v}]`)
}
$check (v) {
if (typeof v._vi === 'undefined') throw new Error(`Invalid calculator[${v}]`)
}
add (v) {
this.check(v)
return super.add(v)
}
$add (v) {
this.$check(v)
return super.$add(v)
}
sub (v) {
this.check(v)
return super.sub(v)
}
$sub (v) {
this.$check(v)
return super.$sub(v)
}
mul (v) {
this.check(v)
return super.mul(v)
}
$mul (v) {
this.$check(v)
return super.$mul(v)
}
div (v) {
this.check(v)
return super.div(v)
}
$div (v) {
this.$check(v)
return super.$div(v)
}
}
function calculator (v) {
return new calculator.Calculator(v)
}
function strict (v) {
return new StrictCalculator(v)
}
function withStrict () {
calculator.Calculator = StrictCalculator;
return this;
}
function withoutStrict () {
calculator.Calculator = Calculator;
return this;
}
Calculator.prototype._print = console.error.bind(console, '[debug]')
calculator.setDebug = function (print) {
Calculator.prototype._print = print
}
calculator.resetDebug = function () {
Calculator.prototype._print = console.error.bind(console, '[debug]')
}
calculator.Calculator = Calculator
calculator.StrictCalculator = StrictCalculator;
/**
* 初始化一个计算器
*/
calculator.$ = calculator
calculator.strict = strict;
calculator.withStrict = withStrict;
calculator.withoutStrict = withoutStrict;
module.exports = calculator