Skip to content

Commit

Permalink
improved rounding method for decimals
Browse files Browse the repository at this point in the history
  • Loading branch information
bpierre committed Feb 6, 2023
1 parent ffd9798 commit 9bc58de
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 10 deletions.
39 changes: 29 additions & 10 deletions src/dnum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { Decimals, Dnum, Numberish, Value } from "./types";

import { divideAndRound, powerOfTen, splitNumber } from "./utils";
import {
abs,
divideAndRound,
powerOfTen,
roundToPower,
splitNumber,
} from "./utils";

export function isDnum(value: unknown): value is Dnum {
return (
Expand Down Expand Up @@ -126,7 +132,7 @@ export function toParts(
): [whole: bigint, fraction: string | null] {
const [value, decimals] = dnum;

// options.digits can also be passed directly as the third argument
// options.digits can also be passed directly as the second argument
if (typeof optionsOrDigits === "number") {
optionsOrDigits = { digits: optionsOrDigits };
}
Expand All @@ -143,16 +149,29 @@ export function toParts(
const decimalsDivisor = powerOfTen(decimals);

const whole = value / decimalsDivisor;
let fraction = String(value % decimalsDivisor).replace(/^-/, "");

const zeros = "0".repeat(
Math.max(0, String(decimalsDivisor).length - fraction.length - 1),
const fractionValue = abs(value % decimalsDivisor);

let fraction = String(
roundToPower(
BigInt(
// prefix with 1 to keep the leading zeros
"1"
// leading zeros
+ "0".repeat(
Math.max(
0,
String(decimalsDivisor).length - String(fractionValue).length - 1,
),
)
// non zero numbers
+ String(fractionValue),
),
powerOfTen(Math.max(0, decimals - digits)),
),
);

fraction = zeros + divideAndRound(
BigInt(fraction),
powerOfTen(Math.max(0, decimals - digits)),
);
// remove the leading 1 and extra decimal places
fraction = fraction.slice(1, digits + 1);

if (trailingZeros) {
fraction = fraction.padEnd(digits, "0");
Expand Down
10 changes: 10 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ export function powerOfTen(zeroes: number) {
// See https://github.com/codesandbox/codesandbox-client/issues/6706
return BigInt("1" + "0".repeat(zeroes));
}

export function roundToPower(value: bigint, power: bigint) {
const a = (value / power) * power;
const b = a + power;
return (value - a >= b - value) ? b : a;
}

export function abs(value: bigint) {
return value < BigInt(0) ? -value : value;
}
36 changes: 36 additions & 0 deletions test/all.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,42 @@ describe("toParts()", () => {
[0n, "00001"],
);
});
it("rounds decimals properly", () => {
expect(toParts([49999999n, 9], 1)).toEqual([0n, null]);
expect(toParts([50000000n, 9], 1)).toEqual([0n, "1"]);
expect(toParts([49998805n, 9], 2)).toEqual([0n, "05"]);
expect(toParts([90000000n, 9], 1)).toEqual([0n, "1"]);

// 1234.56
expect(toParts([123456n, 2], 1)).toEqual([1234n, "6"]);
expect(toParts([123456n, 2], 2)).toEqual([1234n, "56"]);
expect(toParts([123456n, 2], 3)).toEqual([1234n, "56"]);

// 0.09
expect(toParts([9n, 2], 1)).toEqual([0n, "1"]);
expect(toParts([9n, 2], 2)).toEqual([0n, "09"]);

// 1.09
expect(toParts([109n, 2], 1)).toEqual([1n, "1"]);
expect(toParts([109n, 2], 2)).toEqual([1n, "09"]);

// 0.006
expect(toParts([6n, 3], 1)).toEqual([0n, null]);
expect(toParts([6n, 3], 2)).toEqual([0n, "01"]);
expect(toParts([6n, 3], 3)).toEqual([0n, "006"]);

// 0.049998805;
expect(toParts([49998805n, 9], 0)).toEqual([0n, null]);
expect(toParts([49998805n, 9], 1)).toEqual([0n, null]);
expect(toParts([49998805n, 9], 2)).toEqual([0n, "05"]);
expect(toParts([49998805n, 9], 3)).toEqual([0n, "05"]);
expect(toParts([49998805n, 9], 4)).toEqual([0n, "05"]);
expect(toParts([49998805n, 9], 5)).toEqual([0n, "05"]);
expect(toParts([49998805n, 9], 6)).toEqual([0n, "049999"]);
expect(toParts([49998805n, 9], 7)).toEqual([0n, "0499988"]);
expect(toParts([49998805n, 9], 8)).toEqual([0n, "04999881"]);
expect(toParts([49998805n, 9], 9)).toEqual([0n, "049998805"]);
});
});

describe("toNumber()", () => {
Expand Down

0 comments on commit 9bc58de

Please sign in to comment.