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

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jessealama committed Jun 22, 2024
1 parent fb90e18 commit 149b3c5
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 113 deletions.
29 changes: 29 additions & 0 deletions src/common.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,34 @@ export function countSignificantDigits(s: string): number {
return s.length;
}

export function countDigits(s: string): number {
return s.replace(/[.]/, "").length;
}

export function countFractionaDigits(s: string): number {
let [, fractional] = s.split(".");

if (undefined === fractional) {
return 0;
}

return fractional.length;
}

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
export type DigitOrTen = Digit | 10;

export type RoundingMode =
| "ceil"
| "floor"
| "trunc"
| "halfEven"
| "halfExpand";

export const ROUNDING_MODES: RoundingMode[] = [
"ceil",
"floor",
"trunc",
"halfEven",
"halfExpand",
];
140 changes: 60 additions & 80 deletions src/decimal128.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @author Jesse Alama <[email protected]>
*/

import { Digit, DigitOrTen } from "./common.mjs";
import { Digit, DigitOrTen, RoundingMode, ROUNDING_MODES } from "./common.mjs";
import { Rational } from "./rational.mjs";

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

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

if (s.match(/^[0-9]+[eE][+-]?[0-9]+/)) {
let exp = parseInt(s.split(/[eE]/)[1]);
return 0 - exp;
}

if (s.match(/[.]/)) {
let [lhs, rhs] = s.split(".");
if (s.match(/[eE]/)) {
let beforeExp = lhs.split(/[eE]/)[0];
return beforeExp.length;
}
let [_, rhs] = s.split(".");

return rhs.length;
if (rhs.match(/[eE]/)) {
let [dec, exp] = rhs.split(/[eE]/);
return parseInt(exp) - dec.length;
}

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

type NaNValue = "NaN";
Expand All @@ -68,17 +60,18 @@ type Decimal128Value = NaNValue | InfiniteValue | FiniteValue;
const NAN = "NaN";
const POSITIVE_INFINITY = "Infinity";
const NEGATIVE_INFINITY = "-Infinity";
const TEN_MAX_EXPONENT = new Rational(bigTen, bigOne).scale10(
MAX_SIGNIFICANT_DIGITS
const TEN_MAX_EXPONENT = new Rational(
bigTen ** BigInt(MAX_SIGNIFICANT_DIGITS),
bigOne
);

function pickQuantum(d: Rational, preferredQuantum: number): number {
return preferredQuantum;
}

function validateConstructorData(x: Decimal128Value): void {
function validateConstructorData(x: Decimal128Value): Decimal128Value {
if (x === "NaN" || x === "Infinity" || x === "-Infinity") {
return; // no further validation needed
return x; // no further validation needed
}

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

if (v === "0" || v === "-0") {
return; // no further validation needed
return { cohort: v, quantum: q }; // no further validation needed
}

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

if (absV.cmp(TEN_MAX_EXPONENT) > 0) {
throw new RangeError(`Absolute value of scaled cohort is too big`);
let s = v.toPrecision(MAX_SIGNIFICANT_DIGITS + 1);
let numFractionalDigits = 0;
if (s.match(/[.]/)) {
let [_, rhs] = s.split(".");
numFractionalDigits = rhs.length;
}
if (numFractionalDigits === 0) {
throw new RangeError("Integer too large");
}
let newV = v.round(numFractionalDigits - 1, "halfEven");

return { cohort: newV, quantum: q };
}

return;
return { cohort: v, quantum: q };
}

function handleDecimalNotation(s: string): Decimal128Value {
Expand Down Expand Up @@ -215,16 +219,6 @@ function roundIt(
}
}

type RoundingMode = "ceil" | "floor" | "trunc" | "halfEven" | "halfExpand";

const ROUNDING_MODES: RoundingMode[] = [
"ceil",
"floor",
"trunc",
"halfEven",
"halfExpand",
];

export class Decimal128 {
private readonly cohort:
| "NaN"
Expand Down Expand Up @@ -255,7 +249,7 @@ export class Decimal128 {

let data = handleDecimalNotation(s);

validateConstructorData(data);
data = validateConstructorData(data);

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

public mantissa(): Decimal128 {
let [sig, _] = this.significandAndExponent();
return new Decimal128(sig.toDecimalPlaces(MAX_SIGNIFICANT_DIGITS));
return new Decimal128(sig.toFixed(MAX_SIGNIFICANT_DIGITS));
}

public scale10(n: number): Decimal128 {
Expand Down Expand Up @@ -403,13 +397,13 @@ export class Decimal128 {

private emitDecimal(): string {
let v = this.cohort as Rational;
return v.toDecimalPlaces(MAX_SIGNIFICANT_DIGITS);
return v.toPrecision(MAX_SIGNIFICANT_DIGITS);
}

/**
* Returns a digit string representing this Decimal128.
*/
toString(): string {
toString(opts?: { format?: "decimal" | "exponential" }): string {
if (this.isNaN()) {
return NAN;
}
Expand All @@ -419,8 +413,23 @@ export class Decimal128 {
}

let asDecimalString = this.emitDecimal();
let format = undefined;

if (asDecimalString.length > 20) {
if ("object" === typeof opts && "string" === typeof opts.format) {
if (opts.format === "exponential") {
format = "exponential";
} else if (opts.format === "decimal") {
format = "decimal";
} else {
throw new TypeError(`Invalid toString format "${opts.format}"`);
}
}

if (format === "exponential") {
return this.emitExponential();
}

if (format === undefined && asDecimalString.length > 20) {
return this.emitExponential();
}

Expand Down Expand Up @@ -725,11 +734,11 @@ export class Decimal128 {
let ourQuantum = this.quantum as number;
let theirQuantum = x.quantum as number;
let sum = Rational.add(ourCohort, theirCohort);
let prefferedQuantum = Math.min(ourQuantum, theirQuantum);
let preferredQuantum = Math.min(ourQuantum, theirQuantum);

return Decimal128.fromCohortAndQuantum(
sum,
pickQuantum(sum, prefferedQuantum)
pickQuantum(sum, preferredQuantum)
);
}

Expand Down Expand Up @@ -776,11 +785,11 @@ export class Decimal128 {
let ourExponent = this.quantum as number;
let theirExponent = x.quantum as number;
let difference = Rational.subtract(ourCohort, theirCohort);
let prefferedExponent = Math.min(ourExponent, theirExponent);
let preferredQuantum = Math.min(ourExponent, theirExponent);

return Decimal128.fromCohortAndQuantum(
difference,
pickQuantum(difference, prefferedExponent)
pickQuantum(difference, preferredQuantum)
);
}

Expand Down Expand Up @@ -837,11 +846,11 @@ export class Decimal128 {
let ourExponent = this.quantum as number;
let theirExponent = x.quantum as number;
let product = Rational.multiply(ourCohort, theirCohort);
let prefferedExponent = ourExponent + theirExponent;
let preferredQuantum = ourExponent + theirExponent;

return Decimal128.fromCohortAndQuantum(
product,
pickQuantum(product, prefferedExponent)
pickQuantum(product, preferredQuantum)
);
}

Expand Down Expand Up @@ -951,52 +960,23 @@ export class Decimal128 {
numDecimalDigits: number = 0,
mode: RoundingMode = ROUNDING_MODE_DEFAULT
): Decimal128 {
if (this.isNaN() || !this.isFinite()) {
return this.clone();
}

if (!ROUNDING_MODES.includes(mode)) {
throw new RangeError(`Invalid rounding mode "${mode}"`);
}

if (numDecimalDigits < 0) {
numDecimalDigits = 0;
if (this.isNaN() || !this.isFinite()) {
return this.clone();
}

let s = this.toString();
let [lhs, rhs] = s.split(".");

if (undefined === rhs) {
rhs = "";
if (this.isZero()) {
return this.clone();
}

rhs = rhs + "0".repeat(numDecimalDigits);

let finalIntegerDigit = parseInt(
numDecimalDigits > 0
? rhs.charAt(numDecimalDigits - 1)
: lhs.charAt(lhs.length - 1)
) as Digit;
let firstDecimalDigit = parseInt(rhs.charAt(numDecimalDigits)) as Digit;

if (Number.isNaN(firstDecimalDigit)) {
firstDecimalDigit = 0;
}
let v = this.cohort as Rational;
let q = this.quantum as number;

let roundedFinalDigit = roundIt(
this.isNegative(),
finalIntegerDigit,
firstDecimalDigit,
mode
);
return new Decimal128(
numDecimalDigits > 0
? lhs +
"." +
rhs.substring(0, numDecimalDigits - 1) +
`${roundedFinalDigit}`
: lhs.substring(0, lhs.length - 1) + `${roundedFinalDigit}`
);
let roundedV = v.round(numDecimalDigits, mode);
return Decimal128.fromCohortAndQuantum(roundedV, q);
}

neg(): Decimal128 {
Expand Down
Loading

0 comments on commit 149b3c5

Please sign in to comment.