Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 4e6a696

Browse files
authored
Support conversions to/from BigInt and Number (#108)
* Convert to BigInt and Number * Make constructor work with all Numbers
1 parent 10a4b5c commit 4e6a696

File tree

5 files changed

+146
-25
lines changed

5 files changed

+146
-25
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [14.1.0] - 2024-05-06
11+
12+
### Added
13+
14+
- Support for converting decimals to BigInts (`toBigInt`) and Numbers (`toNumber`).
15+
- Relax the constructor to allow all Number arguments (in addition to BigInts and strings).
16+
1017
## [14.0.0] - 2024-04-30
1118

1219
### Removed

src/decimal128.mts

+47-19
Original file line numberDiff line numberDiff line change
@@ -668,15 +668,6 @@ const DEFAULT_CONSTRUCTOR_OPTIONS: FullySpecifiedConstructorOptions =
668668
normalize: CONSTRUCTOR_SHOULD_NORMALIZE,
669669
});
670670

671-
interface FullySpecifiedArithmeticOperationOptions {
672-
roundingMode: RoundingMode;
673-
}
674-
675-
const DEFAULT_ARITHMETIC_OPERATION_OPTIONS: FullySpecifiedArithmeticOperationOptions =
676-
Object.freeze({
677-
roundingMode: ROUNDING_MODE_DEFAULT,
678-
});
679-
680671
type ToStringFormat = "decimal" | "exponential";
681672
const TOSTRING_FORMATS: string[] = ["decimal", "exponential"];
682673

@@ -799,16 +790,7 @@ export class Decimal128 {
799790
ensureFullySpecifiedConstructorOptions(options);
800791

801792
if ("number" === typeof n) {
802-
if (!Number.isInteger(n)) {
803-
throw new SyntaxError("Non-integer number not permitted");
804-
}
805-
if (!Number.isSafeInteger(n)) {
806-
throw new RangeError(
807-
"Integer is too large to be exactly represented"
808-
);
809-
}
810-
811-
s = n.toString();
793+
s = Object.is(n, -0) ? "-0" : n.toString();
812794
} else if ("bigint" === typeof n) {
813795
s = n.toString();
814796
} else {
@@ -938,6 +920,52 @@ export class Decimal128 {
938920
return this.emitDecimal(options);
939921
}
940922

923+
private isInteger(): boolean {
924+
let s = this.toString();
925+
926+
let [_, rhs] = s.split(/[.]/);
927+
928+
if (rhs === undefined) {
929+
return true;
930+
}
931+
932+
return !!rhs.match(/^0+$/);
933+
}
934+
935+
toBigInt(): bigint {
936+
if (this.isNaN) {
937+
throw new RangeError("NaN cannot be converted to a BigInt");
938+
}
939+
940+
if (!this.isFinite) {
941+
throw new RangeError("Infinity cannot be converted to a BigInt");
942+
}
943+
944+
if (!this.isInteger()) {
945+
throw new RangeError(
946+
"Non-integer decimal cannot be converted to a BigInt"
947+
);
948+
}
949+
950+
return BigInt(this.toString());
951+
}
952+
953+
toNumber(): number {
954+
if (this.isNaN) {
955+
return NaN;
956+
}
957+
958+
if (!this.isFinite) {
959+
if (this.isNegative) {
960+
return -Infinity;
961+
}
962+
963+
return Infinity;
964+
}
965+
966+
return Number(this.toString());
967+
}
968+
941969
/**
942970
* Compare two values. Return
943971
*

tests/constructor.test.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -587,18 +587,18 @@ describe("number arguments", () => {
587587
expect(new Decimal128(42).toString()).toStrictEqual("42");
588588
});
589589
test("non-integer number", () => {
590-
expect(() => new Decimal128(42.5)).toThrow(SyntaxError);
590+
expect(new Decimal128(42.5).toString()).toStrictEqual("42.5");
591591
});
592592
test("NaN", () => {
593-
expect(() => new Decimal128(NaN)).toThrow(SyntaxError);
593+
expect(new Decimal128(NaN).toString()).toStrictEqual("NaN");
594594
});
595595
test("minus zero", () => {
596-
expect(new Decimal128(-0).toString()).toStrictEqual("0");
596+
expect(new Decimal128(-0).toString()).toStrictEqual("-0");
597597
});
598-
test("too big", () => {
598+
test("very large value gets approximated", () => {
599599
expect(
600-
() => new Decimal128(123456789012345678901234567890123456789)
601-
).toThrow(RangeError);
600+
new Decimal128(123456789012345678901234567890123456789).toString()
601+
).toStrictEqual("123456789012345680000000000000000000000");
602602
});
603603
});
604604

tests/tobigint.test.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Decimal128 } from "../src/decimal128.mjs";
2+
import { expectDecimal128 } from "./util.js";
3+
4+
describe("NaN", () => {
5+
test("does not work", () => {
6+
expect(() => new Decimal128("NaN").toBigInt()).toThrow(RangeError);
7+
});
8+
});
9+
10+
describe("zero", () => {
11+
test("positive zero", () => {
12+
expect(new Decimal128("0").toBigInt()).toStrictEqual(0n);
13+
});
14+
test("negative zero", () => {
15+
expect(new Decimal128("-0").toBigInt()).toStrictEqual(0n);
16+
});
17+
});
18+
19+
describe("infinity", () => {
20+
test("positive", () => {
21+
expect(() => new Decimal128("Infinity").toBigInt()).toThrow(RangeError);
22+
});
23+
test("negative", () => {
24+
expect(() => new Decimal128("-Infinity").toBigInt()).toThrow(
25+
RangeError
26+
);
27+
});
28+
});
29+
30+
describe("non-integer", () => {
31+
test("throws", () => {
32+
expect(() => new Decimal128("1.2").toBigInt()).toThrow(RangeError);
33+
});
34+
test("work with mathematical value (ignore trailing zeroes)", () => {
35+
expect(new Decimal128("1.00").toBigInt()).toStrictEqual(1n);
36+
});
37+
});
38+
39+
describe("simple examples", () => {
40+
test("42", () => {
41+
expect(new Decimal128("42").toBigInt()).toStrictEqual(42n);
42+
});
43+
test("-123", () => {
44+
expect(new Decimal128("-123").toBigInt()).toStrictEqual(-123n);
45+
});
46+
});

tests/tonumber.test.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Decimal128 } from "../src/decimal128.mjs";
2+
import { expectDecimal128 } from "./util.js";
3+
4+
describe("NaN", () => {
5+
test("works", () => {
6+
expect(new Decimal128("NaN").toNumber()).toStrictEqual(NaN);
7+
});
8+
});
9+
10+
describe("zero", () => {
11+
test("positive zero", () => {
12+
expect(new Decimal128("0").toNumber()).toStrictEqual(0);
13+
});
14+
test("negative zero", () => {
15+
expect(new Decimal128("-0").toNumber()).toStrictEqual(-0);
16+
});
17+
});
18+
19+
describe("infinity", () => {
20+
test("positive infinity", () => {
21+
expect(new Decimal128("Infinity").toNumber()).toStrictEqual(Infinity);
22+
});
23+
test("negative infinity", () => {
24+
expect(new Decimal128("-Infinity").toNumber()).toStrictEqual(-Infinity);
25+
});
26+
});
27+
28+
describe("simple examples", () => {
29+
test("1.25", () => {
30+
expect(new Decimal128("1.25").toNumber()).toStrictEqual(1.25);
31+
});
32+
test("0.1", () => {
33+
expect(new Decimal128("0.1").toNumber()).toStrictEqual(0.1);
34+
});
35+
test("extreme precision", () => {
36+
expect(
37+
new Decimal128("0." + "0".repeat(100) + "1").toNumber()
38+
).toStrictEqual(1e-101);
39+
});
40+
});

0 commit comments

Comments
 (0)