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

Commit 149b3c5

Browse files
committed
WIP
1 parent fb90e18 commit 149b3c5

File tree

5 files changed

+288
-113
lines changed

5 files changed

+288
-113
lines changed

src/common.mts

+29
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,34 @@ export function countSignificantDigits(s: string): number {
2929
return s.length;
3030
}
3131

32+
export function countDigits(s: string): number {
33+
return s.replace(/[.]/, "").length;
34+
}
35+
36+
export function countFractionaDigits(s: string): number {
37+
let [, fractional] = s.split(".");
38+
39+
if (undefined === fractional) {
40+
return 0;
41+
}
42+
43+
return fractional.length;
44+
}
45+
3246
export type Digit = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; // -1 signals that we're moving from the integer part to the decimal part of a decimal number
3347
export type DigitOrTen = Digit | 10;
48+
49+
export type RoundingMode =
50+
| "ceil"
51+
| "floor"
52+
| "trunc"
53+
| "halfEven"
54+
| "halfExpand";
55+
56+
export const ROUNDING_MODES: RoundingMode[] = [
57+
"ceil",
58+
"floor",
59+
"trunc",
60+
"halfEven",
61+
"halfExpand",
62+
];

src/decimal128.mts

+60-80
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* @author Jesse Alama <[email protected]>
1414
*/
1515

16-
import { Digit, DigitOrTen } from "./common.mjs";
16+
import { Digit, DigitOrTen, RoundingMode, ROUNDING_MODES } from "./common.mjs";
1717
import { Rational } from "./rational.mjs";
1818

1919
const EXPONENT_MIN = -6176;
@@ -32,26 +32,18 @@ function quantum(s: string): number {
3232
return quantum(s.substring(1));
3333
}
3434

35-
if (s.match(/^[0-9]+/)) {
35+
if (!s.match(/[.]/)) {
3636
return 0;
3737
}
3838

39-
if (s.match(/^[0-9]+[eE][+-]?[0-9]+/)) {
40-
let exp = parseInt(s.split(/[eE]/)[1]);
41-
return 0 - exp;
42-
}
43-
44-
if (s.match(/[.]/)) {
45-
let [lhs, rhs] = s.split(".");
46-
if (s.match(/[eE]/)) {
47-
let beforeExp = lhs.split(/[eE]/)[0];
48-
return beforeExp.length;
49-
}
39+
let [_, rhs] = s.split(".");
5040

51-
return rhs.length;
41+
if (rhs.match(/[eE]/)) {
42+
let [dec, exp] = rhs.split(/[eE]/);
43+
return parseInt(exp) - dec.length;
5244
}
5345

54-
throw new SyntaxError(`Cannot determine quantum for "${s}"`);
46+
return 0 - rhs.length;
5547
}
5648

5749
type NaNValue = "NaN";
@@ -68,17 +60,18 @@ type Decimal128Value = NaNValue | InfiniteValue | FiniteValue;
6860
const NAN = "NaN";
6961
const POSITIVE_INFINITY = "Infinity";
7062
const NEGATIVE_INFINITY = "-Infinity";
71-
const TEN_MAX_EXPONENT = new Rational(bigTen, bigOne).scale10(
72-
MAX_SIGNIFICANT_DIGITS
63+
const TEN_MAX_EXPONENT = new Rational(
64+
bigTen ** BigInt(MAX_SIGNIFICANT_DIGITS),
65+
bigOne
7366
);
7467

7568
function pickQuantum(d: Rational, preferredQuantum: number): number {
7669
return preferredQuantum;
7770
}
7871

79-
function validateConstructorData(x: Decimal128Value): void {
72+
function validateConstructorData(x: Decimal128Value): Decimal128Value {
8073
if (x === "NaN" || x === "Infinity" || x === "-Infinity") {
81-
return; // no further validation needed
74+
return x; // no further validation needed
8275
}
8376

8477
let val = x as FiniteValue;
@@ -94,7 +87,7 @@ function validateConstructorData(x: Decimal128Value): void {
9487
}
9588

9689
if (v === "0" || v === "-0") {
97-
return; // no further validation needed
90+
return { cohort: v, quantum: q }; // no further validation needed
9891
}
9992

10093
let scaledV = v.scale10(0 - q);
@@ -112,10 +105,21 @@ function validateConstructorData(x: Decimal128Value): void {
112105
}
113106

114107
if (absV.cmp(TEN_MAX_EXPONENT) > 0) {
115-
throw new RangeError(`Absolute value of scaled cohort is too big`);
108+
let s = v.toPrecision(MAX_SIGNIFICANT_DIGITS + 1);
109+
let numFractionalDigits = 0;
110+
if (s.match(/[.]/)) {
111+
let [_, rhs] = s.split(".");
112+
numFractionalDigits = rhs.length;
113+
}
114+
if (numFractionalDigits === 0) {
115+
throw new RangeError("Integer too large");
116+
}
117+
let newV = v.round(numFractionalDigits - 1, "halfEven");
118+
119+
return { cohort: newV, quantum: q };
116120
}
117121

118-
return;
122+
return { cohort: v, quantum: q };
119123
}
120124

121125
function handleDecimalNotation(s: string): Decimal128Value {
@@ -215,16 +219,6 @@ function roundIt(
215219
}
216220
}
217221

218-
type RoundingMode = "ceil" | "floor" | "trunc" | "halfEven" | "halfExpand";
219-
220-
const ROUNDING_MODES: RoundingMode[] = [
221-
"ceil",
222-
"floor",
223-
"trunc",
224-
"halfEven",
225-
"halfExpand",
226-
];
227-
228222
export class Decimal128 {
229223
private readonly cohort:
230224
| "NaN"
@@ -255,7 +249,7 @@ export class Decimal128 {
255249

256250
let data = handleDecimalNotation(s);
257251

258-
validateConstructorData(data);
252+
data = validateConstructorData(data);
259253

260254
if (data === "NaN" || data === "Infinity" || data === "-Infinity") {
261255
this.cohort = data;
@@ -348,7 +342,7 @@ export class Decimal128 {
348342

349343
public mantissa(): Decimal128 {
350344
let [sig, _] = this.significandAndExponent();
351-
return new Decimal128(sig.toDecimalPlaces(MAX_SIGNIFICANT_DIGITS));
345+
return new Decimal128(sig.toFixed(MAX_SIGNIFICANT_DIGITS));
352346
}
353347

354348
public scale10(n: number): Decimal128 {
@@ -403,13 +397,13 @@ export class Decimal128 {
403397

404398
private emitDecimal(): string {
405399
let v = this.cohort as Rational;
406-
return v.toDecimalPlaces(MAX_SIGNIFICANT_DIGITS);
400+
return v.toPrecision(MAX_SIGNIFICANT_DIGITS);
407401
}
408402

409403
/**
410404
* Returns a digit string representing this Decimal128.
411405
*/
412-
toString(): string {
406+
toString(opts?: { format?: "decimal" | "exponential" }): string {
413407
if (this.isNaN()) {
414408
return NAN;
415409
}
@@ -419,8 +413,23 @@ export class Decimal128 {
419413
}
420414

421415
let asDecimalString = this.emitDecimal();
416+
let format = undefined;
422417

423-
if (asDecimalString.length > 20) {
418+
if ("object" === typeof opts && "string" === typeof opts.format) {
419+
if (opts.format === "exponential") {
420+
format = "exponential";
421+
} else if (opts.format === "decimal") {
422+
format = "decimal";
423+
} else {
424+
throw new TypeError(`Invalid toString format "${opts.format}"`);
425+
}
426+
}
427+
428+
if (format === "exponential") {
429+
return this.emitExponential();
430+
}
431+
432+
if (format === undefined && asDecimalString.length > 20) {
424433
return this.emitExponential();
425434
}
426435

@@ -725,11 +734,11 @@ export class Decimal128 {
725734
let ourQuantum = this.quantum as number;
726735
let theirQuantum = x.quantum as number;
727736
let sum = Rational.add(ourCohort, theirCohort);
728-
let prefferedQuantum = Math.min(ourQuantum, theirQuantum);
737+
let preferredQuantum = Math.min(ourQuantum, theirQuantum);
729738

730739
return Decimal128.fromCohortAndQuantum(
731740
sum,
732-
pickQuantum(sum, prefferedQuantum)
741+
pickQuantum(sum, preferredQuantum)
733742
);
734743
}
735744

@@ -776,11 +785,11 @@ export class Decimal128 {
776785
let ourExponent = this.quantum as number;
777786
let theirExponent = x.quantum as number;
778787
let difference = Rational.subtract(ourCohort, theirCohort);
779-
let prefferedExponent = Math.min(ourExponent, theirExponent);
788+
let preferredQuantum = Math.min(ourExponent, theirExponent);
780789

781790
return Decimal128.fromCohortAndQuantum(
782791
difference,
783-
pickQuantum(difference, prefferedExponent)
792+
pickQuantum(difference, preferredQuantum)
784793
);
785794
}
786795

@@ -837,11 +846,11 @@ export class Decimal128 {
837846
let ourExponent = this.quantum as number;
838847
let theirExponent = x.quantum as number;
839848
let product = Rational.multiply(ourCohort, theirCohort);
840-
let prefferedExponent = ourExponent + theirExponent;
849+
let preferredQuantum = ourExponent + theirExponent;
841850

842851
return Decimal128.fromCohortAndQuantum(
843852
product,
844-
pickQuantum(product, prefferedExponent)
853+
pickQuantum(product, preferredQuantum)
845854
);
846855
}
847856

@@ -951,52 +960,23 @@ export class Decimal128 {
951960
numDecimalDigits: number = 0,
952961
mode: RoundingMode = ROUNDING_MODE_DEFAULT
953962
): Decimal128 {
954-
if (this.isNaN() || !this.isFinite()) {
955-
return this.clone();
956-
}
957-
958963
if (!ROUNDING_MODES.includes(mode)) {
959964
throw new RangeError(`Invalid rounding mode "${mode}"`);
960965
}
961966

962-
if (numDecimalDigits < 0) {
963-
numDecimalDigits = 0;
967+
if (this.isNaN() || !this.isFinite()) {
968+
return this.clone();
964969
}
965970

966-
let s = this.toString();
967-
let [lhs, rhs] = s.split(".");
968-
969-
if (undefined === rhs) {
970-
rhs = "";
971+
if (this.isZero()) {
972+
return this.clone();
971973
}
972974

973-
rhs = rhs + "0".repeat(numDecimalDigits);
974-
975-
let finalIntegerDigit = parseInt(
976-
numDecimalDigits > 0
977-
? rhs.charAt(numDecimalDigits - 1)
978-
: lhs.charAt(lhs.length - 1)
979-
) as Digit;
980-
let firstDecimalDigit = parseInt(rhs.charAt(numDecimalDigits)) as Digit;
981-
982-
if (Number.isNaN(firstDecimalDigit)) {
983-
firstDecimalDigit = 0;
984-
}
975+
let v = this.cohort as Rational;
976+
let q = this.quantum as number;
985977

986-
let roundedFinalDigit = roundIt(
987-
this.isNegative(),
988-
finalIntegerDigit,
989-
firstDecimalDigit,
990-
mode
991-
);
992-
return new Decimal128(
993-
numDecimalDigits > 0
994-
? lhs +
995-
"." +
996-
rhs.substring(0, numDecimalDigits - 1) +
997-
`${roundedFinalDigit}`
998-
: lhs.substring(0, lhs.length - 1) + `${roundedFinalDigit}`
999-
);
978+
let roundedV = v.round(numDecimalDigits, mode);
979+
return Decimal128.fromCohortAndQuantum(roundedV, q);
1000980
}
1001981

1002982
neg(): Decimal128 {

0 commit comments

Comments
 (0)