bignumber.js precise decimals: working versions (tested 2026-10-15)
Do exact decimal math with bignumber.js: 0.1 + 0.2 === 0.3, configurable rounding modes, fixed-point formatting, numeric comparisons and a cart-total helper, all offline.
passed7 passed · 0 failed2026-10-15 22:58 UTC- Status
- passed
- Last passed
- 2026-10-15 22:58 UTC
- Tests
- 7 / 7
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
bignumber.js@11.1.5@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38820570375
The code
Exactly the files that passed. Your agent can pull them with get_recipe("bignumber-js-precise-decimals") (install), or see them on GitHub.
package.json
{
"name": "bignumber-js-precise-decimals",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {"bignumber.js":"11.1.5"},
"devDependencies": {"@types/node":"26.6.5","tsx":"4.23.15"}
}
src/decimals.ts
import BigNumber from "bignumber.js";
BigNumber.config({ DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP });
export type RoundingMode =
| "up"
| "down"
| "ceil"
| "floor"
| "half-up"
| "half-down"
| "half-even";
const ROUNDING: Record<RoundingMode, number> = {
up: BigNumber.ROUND_UP,
down: BigNumber.ROUND_DOWN,
ceil: BigNumber.ROUND_CEIL,
floor: BigNumber.ROUND_FLOOR,
"half-up": BigNumber.ROUND_HALF_UP,
"half-down": BigNumber.ROUND_HALF_DOWN,
"half-even": BigNumber.ROUND_HALF_EVEN,
};
export type Money = string | number;
export function exact(value: Money): BigNumber {
return new BigNumber(value);
}
export function add(a: Money, b: Money): BigNumber {
return new BigNumber(a).plus(b);
}
export function multiply(a: Money, b: Money): BigNumber {
return new BigNumber(a).times(b);
}
export function round(value: Money, places: number, mode: RoundingMode = "half-up"): BigNumber {
return new BigNumber(value).decimalPlaces(places, ROUNDING[mode]);
}
export function formatFixed(value: Money, places: number, mode: RoundingMode = "half-up"): string {
return new BigNumber(value).toFixed(places, ROUNDING[mode]);
}
export function compare(a: Money, b: Money): number {
return new BigNumber(a).comparedTo(b);
}
export type CartLine = {
name: string;
price: Money;
qty: number;
};
export function cartTotal(lines: CartLine[], taxRate: Money = "0"): string {
const subtotal = lines.reduce(
(acc, line) => acc.plus(new BigNumber(line.price).times(line.qty)),
new BigNumber(0),
);
const tax = subtotal.times(taxRate);
return subtotal.plus(tax).toFixed(2, BigNumber.ROUND_HALF_UP);
}
test/decimals.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import {
add,
multiply,
round,
formatFixed,
compare,
cartTotal,
} from "../src/decimals.ts";
test("0.1 + 0.2 is exact", () => {
assert.equal(add(0.1, 0.2).toFixed(), "0.3");
assert.notEqual(0.1 + 0.2, 0.3);
assert.equal(add("0.1", "0.2").toString(), "0.3");
});
test("multiply avoids binary float drift", () => {
assert.equal(multiply("1.1", "1.1").toFixed(), "1.21");
assert.equal(multiply(0.07, 100).toFixed(), "7");
});
test("honours the requested rounding mode", () => {
assert.equal(round("2.345", 2, "half-up").toFixed(2), "2.35");
assert.equal(round("2.345", 2, "half-even").toFixed(2), "2.34");
assert.equal(round("2.355", 2, "half-even").toFixed(2), "2.36");
assert.equal(round("2.341", 2, "down").toFixed(2), "2.34");
assert.equal(round("2.341", 2, "up").toFixed(2), "2.35");
assert.equal(round("-2.341", 2, "floor").toFixed(2), "-2.35");
assert.equal(round("2.341", 2, "ceil").toFixed(2), "2.35");
});
test("fixed-point formatting", () => {
assert.equal(formatFixed("1.005", 2, "half-up"), "1.01");
assert.equal(formatFixed("1.005", 2, "half-even"), "1.00");
assert.equal(formatFixed("1", 4), "1.0000");
assert.equal(formatFixed("-0.5", 0, "half-up"), "-1");
});
test("numeric comparisons ignore formatting", () => {
assert.equal(compare("0.1", "0.10"), 0);
assert.equal(compare("10", "2"), 1);
assert.equal(compare("2", "10"), -1);
assert.equal(compare("0.30000000000000004", "0.3"), 1);
});
test("cart total applies tax and rounds to cents", () => {
const cart = [
{ name: "Coffee", price: "2.50", qty: 3 },
{ name: "Bagel", price: "1.10", qty: 2 },
];
assert.equal(cartTotal(cart), "9.70");
assert.equal(cartTotal(cart, "0.08"), "10.48");
});
test("cart total is exact for values that drift in binary", () => {
const cart = [
{ name: "A", price: "0.1", qty: 1 },
{ name: "B", price: "0.2", qty: 1 },
];
assert.equal(cartTotal(cart), "0.30");
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.