提问人:SnakeDoc 提问时间:5/25/2013 最后编辑:Samuel PhilippSnakeDoc 更新时间:11/5/2023 访问量:79986
JavaScript 中的 BigDecimal
BigDecimal in JavaScript
问:
我对 JavaScript 非常陌生(我有 Java 背景),我正在尝试用少量资金进行一些财务计算。
我最初的想法是:
<script type="text/javascript">
var normBase = ("[price]").replace("$", "");
var salesBase = ("[saleprice]").replace("$", "");
var base;
if (salesBase != 0) {
base = salesBase;
} else {
base = normBase;
}
var per5 = (base - (base * 0.05));
var per7 = (base - (base * 0.07));
var per10 = (base - (base * 0.10));
var per15 = (base - (base * 0.15));
document.write
(
'5% Off: $' + (Math.ceil(per5 * 100) / 100).toFixed(2) + '<br/>' +
'7% Off: $' + (Math.ceil(per7 * 100) / 100).toFixed(2) + '<br/>' +
'10% Off: $' + (Math.ceil(per10 * 100) / 100).toFixed(2) + '<br/>' +
'15% Off: $' + (Math.ceil(per15 * 100) / 100).toFixed(2) + '<br/>'
);
</script>
这效果很好,除了它总是四舍五入 ()。 有同样的问题,对浮动也没有好处。Math.ceil
Math.floor
Math.round
在 Java 中,我会从一开始就完全避免使用浮点数,但是在 JavaScript 中,似乎没有默认包含可比较的东西。
问题是,提到的所有库要么被破坏,要么出于不同的目的。该库非常接近我需要的东西,但是我在四舍五入和精度方面遇到了奇怪的问题......无论我将 Round Type 设置为什么,它似乎都可以自行决定。例如,精度为 2 的 3.7107 和圆形类型不知何故最终为 3.72,而它应该是 3.71。jsfromhell.com/classes/bignumber
ROUND_HALF_UP
我还尝试@JasonSmith BigDecimal 库(Java 的 BigDecimal 的机械端口),但它似乎是用于 node 的.js我没有运行选项。
我如何使用普通的 JavaScript(并且可靠)来实现这一点,或者是否有现代(上面提到的那些现在都已经有几年的历史了)库可供我使用,该库已得到维护且未损坏?
答:
我喜欢使用数字、货币和货币格式。accounting.js
主页 - https://openexchangerates.github.io/accounting.js/
Github的 - https://github.com/openexchangerates/accounting.js
评论
js 中有几种 BigDecimal 实现:
最后 3 个来自同一个作者:看看区别。
评论
Big.js
很棒,但对我来说太笨重了。
我目前正在使用以下内容,它使用 BigInt 进行任意精度。仅支持加、减、乘、除。调用将精度设置为小数点后 8 位。set_precision(8);
舍入模式ROUND_DOWN。
class AssertionError extends Error {
/**
* @param {String|void} message
*/
constructor (message) {
super(message);
this.name = 'AssertionError';
if (Error.captureStackTrace instanceof Function) {
Error.captureStackTrace(this, AssertionError);
}
}
toJSON () {
return { name: this.name, message: this.message, stack: this.stack };
}
/**
* @param {Boolean} value
* @param {String|void} message
*/
static assert (value, message) {
if (typeof value !== 'boolean') {
throw new Error('assert(value, message?), "value" must be a boolean.');
}
if (message !== undefined && typeof message !== 'string') {
throw new Error('assert(value, message?), "message" must be a string.');
}
if (value === false) {
throw new AssertionError(message);
}
}
}
module.exports = AssertionError;
const AssertionError = require('./AssertionError');
let precision = 2;
let precision_multiplier = 10n ** BigInt(precision);
let max_safe_integer = BigInt(Number.MAX_SAFE_INTEGER) * precision_multiplier;
/**
* @param {Number} value
*/
const set_precision = (value) => {
AssertionError.assert(typeof value === 'number');
AssertionError.assert(Number.isFinite(value) === true);
AssertionError.assert(Number.isInteger(value) === true);
AssertionError.assert(value >= 0 === true);
precision = value;
precision_multiplier = 10n ** BigInt(precision);
max_safe_integer = BigInt(Number.MAX_SAFE_INTEGER) * precision_multiplier;
};
/**
* @param {Number} value
*/
const to_bigint = (value) => {
AssertionError.assert(typeof value === 'number');
AssertionError.assert(Number.isFinite(value) === true);
return BigInt(value.toFixed(precision).replace('.', ''));
};
/**
* @param {BigInt} value
* @param {Number} decimal_places
*/
const to_number = (value) => {
AssertionError.assert(typeof value === 'bigint');
AssertionError.assert(value <= max_safe_integer);
const value_string = value.toString().padStart(2 + precision, '0');
const whole = value_string.substring(0, value_string.length - precision);
const decimal = value_string.substring(value_string.length - precision, value_string.length);
const result = Number(`${whole}.${decimal}`);
return result;
};
/**
* @param {Number[]} values
*/
const add = (...values) => to_number(values.reduce((previous, current) => previous === null ? to_bigint(current) : previous + to_bigint(current), null));
const subtract = (...values) => to_number(values.reduce((previous, current) => previous === null ? to_bigint(current) : previous - to_bigint(current), null));
const multiply = (...values) => to_number(values.reduce((previous, current) => previous === null ? to_bigint(current) : (previous * to_bigint(current)) / precision_multiplier, null));
const divide = (...values) => to_number(values.reduce((previous, current) => previous === null ? to_bigint(current) : (previous * precision_multiplier) / to_bigint(current), null));
const arbitrary = { set_precision, add, subtract, multiply, divide };
module.exports = arbitrary;
const arbitrary = require('./arbitrary');
arbitrary.set_precision(2);
const add = arbitrary.add;
const subtract = arbitrary.subtract;
const multiply = arbitrary.multiply;
const divide = arbitrary.divide;
console.log(add(75, 25, 25)); // 125
console.log(subtract(75, 25, 25)); // 25
console.log(multiply(5, 5)); // 25
console.log(add(5, multiply(5, 5))); // 30
console.log(divide(125, 5, 5)); // 5
console.log(divide(1000, 10, 10)); // 10
console.log(divide(1000, 8.86)); // 112.86681715
console.log(add(Number.MAX_SAFE_INTEGER, 0)); // 9007199254740991
console.log(subtract(Number.MAX_SAFE_INTEGER, 1)); // 9007199254740990
console.log(multiply(Number.MAX_SAFE_INTEGER, 0.5)); // 4503599627370495.5
console.log(divide(Number.MAX_SAFE_INTEGER, 2)); // 4503599627370495.5
console.log(multiply(Math.PI, Math.PI)); // 9.86960437
console.log(divide(Math.PI, Math.PI)); // 1
console.log(divide(1, 12)); // 0.08333333
console.log(add(0.1, 0.2)); // 0.3
console.log(multiply(1.500, 1.3)); // 1.95
console.log(multiply(0, 1)); // 0
console.log(multiply(0, -1)); // 0
console.log(multiply(-1, 1)); // -1
console.log(divide(1.500, 1.3)); // 1.15384615
console.log(divide(0, 1)); // 0
console.log(divide(0, -1)); // 0
console.log(divide(-1, 1)); // -1
console.log(multiply(5, 5, 5, 5)); // 625
console.log(multiply(5, 5, 5, 123, 123, 5)); // 9455625
评论
由于我们有原生支持,因此不再需要太多代码来实现。BigInt
BigDecimal
下面是一个基于以下特征的类:BigDecimal
BigInt
- 小数点数配置为常量,适用于所有实例。
- 过多的数字是被截断还是舍入被配置为布尔常量。
- 实例将十进制数存储为 a ,乘以 10 的幂,以便包含小数。
BigInt
- 所有计算都使用这些值进行。
BigInt
- 传递给 、 和 的参数可以是数字、字符串或
add
subtract
multiply
divide
BigDecimal
- 这些方法返回新实例,因此 a 被视为不可变的。
BigDecimal
- 该方法重新引入小数点。
toString
- A 可以强制使用一个数字(通过隐式调用 ),但这显然会导致精度的损失。
BigDecimal
toString
class BigDecimal {
// Configuration: constants
static DECIMALS = 18; // number of decimals on all instances
static ROUNDED = true; // numbers are truncated (false) or rounded (true)
static SHIFT = BigInt("1" + "0".repeat(BigDecimal.DECIMALS)); // derived constant
constructor(value) {
if (value instanceof BigDecimal) return value;
let [ints, decis] = String(value).split(".").concat("");
this._n = BigInt(ints + decis.padEnd(BigDecimal.DECIMALS, "0")
.slice(0, BigDecimal.DECIMALS))
+ BigInt(BigDecimal.ROUNDED && decis[BigDecimal.DECIMALS] >= "5");
}
static fromBigInt(bigint) {
return Object.assign(Object.create(BigDecimal.prototype), { _n: bigint });
}
add(num) {
return BigDecimal.fromBigInt(this._n + new BigDecimal(num)._n);
}
subtract(num) {
return BigDecimal.fromBigInt(this._n - new BigDecimal(num)._n);
}
static _divRound(dividend, divisor) {
return BigDecimal.fromBigInt(dividend / divisor
+ (BigDecimal.ROUNDED ? dividend * 2n / divisor % 2n : 0n));
}
multiply(num) {
return BigDecimal._divRound(this._n * new BigDecimal(num)._n, BigDecimal.SHIFT);
}
divide(num) {
return BigDecimal._divRound(this._n * BigDecimal.SHIFT, new BigDecimal(num)._n);
}
toString() {
let s = this._n.toString().replace("-", "").padStart(BigDecimal.DECIMALS+1, "0");
s = (s.slice(0, -BigDecimal.DECIMALS) + "." + s.slice(-BigDecimal.DECIMALS))
.replace(/(\.0*|0+)$/, "");
return this._n < 0 ? "-" + s : s;
}
}
// Demo
var a = new BigDecimal("123456789123456789876");
var b = a.divide("10000000000000000000");
var c = b.add("9.000000000000000004");
console.log(b.toString());
console.log(c.toString());
console.log(+c); // loss of precision when converting to number
评论
将 @trincot 的 BigDecimal 的出色实现包装到一个 NPM 模块中,并结合了 BigInt polyfill JSBI 和 Reverse Polish 表示法算法。
有了这个模块,现在可以在JS中执行任意算术计算,甚至与IE11兼容。
npm 安装 jsbi 计算器
import JBC from "jsbi-calculator";
const { calculator } = JBC;
const expressionOne = "((10 * (24 / ((9 + 3) * (-2)))) + 17) + 5";
const resultOne = calculator(expressionOne);
console.log(resultOne);
// -> '12'
const max = String(Number.MAX_SAFE_INTEGER);
console.log(max);
// -> '9007199254740991'
const expressionTwo = `${max} + 2`;
const resultTwo = calculator(expressionTwo);
console.log(resultTwo);
// -> '9007199254740993'
这是指向 npm 页面的链接。https://www.npmjs.com/package/jsbi-calculator。
再次感谢@trincot的灵感。
评论
BigInt