refactor: replace ignored numeric literals with constants
This commit is contained in:
+37
-21
@@ -1,3 +1,19 @@
|
||||
export const DECIMAL_RADIX = 10n;
|
||||
|
||||
const SIGN_LENGTH = 1;
|
||||
const NEGATIVE_SIGN = -1n;
|
||||
const POSITIVE_SIGN = 1n;
|
||||
const ZERO_UNITS = 0n;
|
||||
const EQUAL = 0;
|
||||
const GREATER_THAN = 1;
|
||||
const LESS_THAN = -1;
|
||||
const INTEGER_SCALE = 0;
|
||||
const MIN_WHOLE_DIGITS = 1;
|
||||
const DIGITS_START = 0;
|
||||
const PREVIOUS_DIGIT_OFFSET = 1;
|
||||
const DIGIT_STEP = 1;
|
||||
const ROUNDING_DIVISOR = 2n;
|
||||
|
||||
export type Decimal = { scale: number; value: bigint };
|
||||
|
||||
export function decimalFromString(value: string): Decimal {
|
||||
@@ -6,9 +22,9 @@ export function decimalFromString(value: string): Decimal {
|
||||
throw new Error('Invalid decimal string');
|
||||
}
|
||||
const negative = value.startsWith('-');
|
||||
const [whole, fraction = ''] = (negative ? value.slice(1) : value).split('.');
|
||||
const [whole, fraction = ''] = (negative ? value.slice(SIGN_LENGTH) : value).split('.');
|
||||
return {
|
||||
value: (negative ? -1n : 1n) * BigInt(`${whole}${fraction}`),
|
||||
value: (negative ? NEGATIVE_SIGN : POSITIVE_SIGN) * BigInt(`${whole}${fraction}`),
|
||||
scale: fraction.length,
|
||||
};
|
||||
}
|
||||
@@ -17,8 +33,8 @@ export function add(left: Decimal, right: Decimal): Decimal {
|
||||
const scale = Math.max(left.scale, right.scale);
|
||||
return {
|
||||
value:
|
||||
left.value * 10n ** BigInt(scale - left.scale) +
|
||||
right.value * 10n ** BigInt(scale - right.scale),
|
||||
left.value * DECIMAL_RADIX ** BigInt(scale - left.scale) +
|
||||
right.value * DECIMAL_RADIX ** BigInt(scale - right.scale),
|
||||
scale,
|
||||
};
|
||||
}
|
||||
@@ -32,37 +48,37 @@ export function multiply(left: Decimal, right: Decimal): Decimal {
|
||||
}
|
||||
|
||||
export function isZero(value: Decimal): boolean {
|
||||
return value.value === 0n;
|
||||
return value.value === ZERO_UNITS;
|
||||
}
|
||||
|
||||
export function compare(left: Decimal, right: Decimal): number {
|
||||
const difference = subtract(left, right).value;
|
||||
return difference === 0n ? 0 : difference > 0n ? 1 : -1;
|
||||
return difference === ZERO_UNITS ? EQUAL : difference > ZERO_UNITS ? GREATER_THAN : LESS_THAN;
|
||||
}
|
||||
|
||||
export function formatDecimal({ value, scale }: Decimal): string {
|
||||
if (value === 0n) return '0';
|
||||
const sign = value < 0n ? '-' : '';
|
||||
if (scale === 0) return `${sign}${value < 0n ? -value : value}`;
|
||||
const digits = (value < 0n ? -value : value)
|
||||
if (value === ZERO_UNITS) return '0';
|
||||
const sign = value < ZERO_UNITS ? '-' : '';
|
||||
if (scale === INTEGER_SCALE) return `${sign}${value < ZERO_UNITS ? -value : value}`;
|
||||
const digits = (value < ZERO_UNITS ? -value : value)
|
||||
.toString()
|
||||
.padStart(scale + 1, '0');
|
||||
const whole = digits.slice(0, -scale) || '0';
|
||||
.padStart(scale + MIN_WHOLE_DIGITS, '0');
|
||||
const whole = digits.slice(DIGITS_START, -scale) || '0';
|
||||
const rawFraction = digits.slice(-scale);
|
||||
let fractionEnd = rawFraction.length;
|
||||
while (rawFraction.charAt(fractionEnd - 1) === '0') fractionEnd -= 1;
|
||||
const fraction = rawFraction.slice(0, fractionEnd);
|
||||
while (rawFraction.charAt(fractionEnd - PREVIOUS_DIGIT_OFFSET) === '0') fractionEnd -= DIGIT_STEP;
|
||||
const fraction = rawFraction.slice(DIGITS_START, fractionEnd);
|
||||
const fractionalPart = fraction ? '.' + fraction : '';
|
||||
return `${sign}${whole}${fractionalPart}`;
|
||||
}
|
||||
|
||||
// Round half away from zero; callers choose presentation precision explicitly.
|
||||
export function divide(left: Decimal, right: Decimal, scale: number): Decimal {
|
||||
if (right.value === 0n) throw new Error('Division by zero');
|
||||
const numerator = left.value * 10n ** BigInt(right.scale + scale);
|
||||
const denominator = right.value * 10n ** BigInt(left.scale);
|
||||
const sign = (numerator < 0n) !== (denominator < 0n) ? -1n : 1n;
|
||||
const n = numerator < 0n ? -numerator : numerator;
|
||||
const d = denominator < 0n ? -denominator : denominator;
|
||||
return { value: sign * ((n + d / 2n) / d), scale };
|
||||
if (right.value === ZERO_UNITS) throw new Error('Division by zero');
|
||||
const numerator = left.value * DECIMAL_RADIX ** BigInt(right.scale + scale);
|
||||
const denominator = right.value * DECIMAL_RADIX ** BigInt(left.scale);
|
||||
const sign = (numerator < ZERO_UNITS) !== (denominator < ZERO_UNITS) ? NEGATIVE_SIGN : POSITIVE_SIGN;
|
||||
const n = numerator < ZERO_UNITS ? -numerator : numerator;
|
||||
const d = denominator < ZERO_UNITS ? -denominator : denominator;
|
||||
return { value: sign * ((n + d / ROUNDING_DIVISOR) / d), scale };
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export const ISO_DATE_LENGTH = 10;
|
||||
export const ISO_DATE_START = 0;
|
||||
export const MILLISECONDS_PER_SECOND = 1_000;
|
||||
export const MILLISECONDS_PER_DAY = 86_400_000;
|
||||
|
||||
Reference in New Issue
Block a user