@kikuchan/decimal 0.1.0-alpha.3 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +1 -0
- package/index.d.cts +109 -0
- package/index.d.ts +109 -0
- package/index.js +1 -0
- package/package.json +12 -5
- package/LICENSE +0 -21
- package/src/index.ts +0 -1020
- package/tests/decimal.spec.ts +0 -1239
- package/tsconfig.json +0 -1
package/src/index.ts
DELETED
|
@@ -1,1020 +0,0 @@
|
|
|
1
|
-
const __brand = Symbol.for('Decimal');
|
|
2
|
-
|
|
3
|
-
export interface DecimalInstance {
|
|
4
|
-
readonly [__brand]: never;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export type DecimalLike = number | string | bigint | DecimalInstance | { coeff: bigint; digits: bigint };
|
|
8
|
-
export type RoundingMode = 'trunc' | 'floor' | 'ceil' | 'round';
|
|
9
|
-
|
|
10
|
-
export interface Decimal {
|
|
11
|
-
readonly [__brand]: never;
|
|
12
|
-
|
|
13
|
-
coeff: bigint;
|
|
14
|
-
digits: bigint;
|
|
15
|
-
|
|
16
|
-
// Copying
|
|
17
|
-
clone(): Decimal;
|
|
18
|
-
|
|
19
|
-
// Rounding and scaling
|
|
20
|
-
round$(digits?: bigint | number, force?: boolean): this;
|
|
21
|
-
round(digits?: bigint | number, force?: boolean): Decimal;
|
|
22
|
-
floor$(digits?: bigint | number, force?: boolean): this;
|
|
23
|
-
floor(digits?: bigint | number, force?: boolean): Decimal;
|
|
24
|
-
ceil$(digits?: bigint | number, force?: boolean): this;
|
|
25
|
-
ceil(digits?: bigint | number, force?: boolean): Decimal;
|
|
26
|
-
trunc$(digits?: bigint | number, force?: boolean): this;
|
|
27
|
-
trunc(digits?: bigint | number, force?: boolean): Decimal;
|
|
28
|
-
rescale$(digits?: bigint | number, mode?: RoundingMode): this;
|
|
29
|
-
rescale(digits?: bigint | number, mode?: RoundingMode): Decimal;
|
|
30
|
-
roundBy$(step: DecimalLike, mode?: RoundingMode): this;
|
|
31
|
-
roundBy(step: DecimalLike, mode?: RoundingMode): Decimal;
|
|
32
|
-
floorBy$(step: DecimalLike): this;
|
|
33
|
-
floorBy(step: DecimalLike): Decimal;
|
|
34
|
-
ceilBy$(step: DecimalLike): this;
|
|
35
|
-
ceilBy(step: DecimalLike): Decimal;
|
|
36
|
-
truncBy$(step: DecimalLike): this;
|
|
37
|
-
truncBy(step: DecimalLike): Decimal;
|
|
38
|
-
split$(digits?: bigint | number, mode?: RoundingMode): [Decimal, Decimal];
|
|
39
|
-
split(digits?: bigint | number, mode?: RoundingMode): [Decimal, Decimal];
|
|
40
|
-
splitBy$(step: DecimalLike, mode?: RoundingMode): [Decimal, Decimal];
|
|
41
|
-
splitBy(step: DecimalLike, mode?: RoundingMode): [Decimal, Decimal];
|
|
42
|
-
|
|
43
|
-
// Sign and absolute
|
|
44
|
-
neg$(flag?: boolean): this;
|
|
45
|
-
neg(flag?: boolean): Decimal;
|
|
46
|
-
abs$(): this;
|
|
47
|
-
abs(): Decimal;
|
|
48
|
-
sign$(): this;
|
|
49
|
-
sign(): Decimal;
|
|
50
|
-
isZero(): boolean;
|
|
51
|
-
isPositive(): boolean;
|
|
52
|
-
isNegative(): boolean;
|
|
53
|
-
|
|
54
|
-
// Arithmetic
|
|
55
|
-
add$(v: DecimalLike): this;
|
|
56
|
-
add(v: DecimalLike): Decimal;
|
|
57
|
-
sub$(v: DecimalLike): this;
|
|
58
|
-
sub(v: DecimalLike): Decimal;
|
|
59
|
-
mul$(v: DecimalLike, digits?: number | bigint | undefined): Decimal;
|
|
60
|
-
mul(v: DecimalLike, digits?: number | bigint | undefined): Decimal;
|
|
61
|
-
shift10$(exponent: bigint | number): this;
|
|
62
|
-
shift10(exponent: bigint | number): Decimal;
|
|
63
|
-
inverse$(digits?: bigint | number): this;
|
|
64
|
-
inverse(digits?: bigint | number): Decimal;
|
|
65
|
-
div$(v: DecimalLike, digits?: bigint | number, mode?: RoundingMode): this;
|
|
66
|
-
div(v: DecimalLike, digits?: bigint | number): Decimal;
|
|
67
|
-
|
|
68
|
-
// Modulo and bounding
|
|
69
|
-
mod$(v: DecimalLike): this;
|
|
70
|
-
mod(v: DecimalLike): Decimal;
|
|
71
|
-
modPositive$(v: DecimalLike): this;
|
|
72
|
-
modPositive(v: DecimalLike): Decimal;
|
|
73
|
-
clamp$(minValue: DecimalLike | undefined, maxValue: DecimalLike | undefined): this;
|
|
74
|
-
clamp(minValue: DecimalLike | undefined, maxValue: DecimalLike | undefined): Decimal;
|
|
75
|
-
|
|
76
|
-
// Comparison
|
|
77
|
-
cmp(v: DecimalLike): number;
|
|
78
|
-
eq(v: DecimalLike): boolean;
|
|
79
|
-
neq(v: DecimalLike): boolean;
|
|
80
|
-
lt(v: DecimalLike): boolean;
|
|
81
|
-
gt(v: DecimalLike): boolean;
|
|
82
|
-
le(v: DecimalLike): boolean;
|
|
83
|
-
ge(v: DecimalLike): boolean;
|
|
84
|
-
between(a: DecimalLike | undefined, b: DecimalLike | undefined): boolean;
|
|
85
|
-
isCloseTo(v: DecimalLike, tolerance: DecimalLike): boolean;
|
|
86
|
-
|
|
87
|
-
// Advanced math
|
|
88
|
-
pow$(exponent: DecimalLike, digits?: bigint | number): this;
|
|
89
|
-
pow(exponent: DecimalLike, digits?: bigint | number): Decimal;
|
|
90
|
-
root$(degree: bigint | number, digits?: bigint | number): this;
|
|
91
|
-
root(degree: bigint | number, digits?: bigint | number): Decimal;
|
|
92
|
-
sqrt$(digits?: bigint | number): this;
|
|
93
|
-
sqrt(digits?: bigint | number): Decimal;
|
|
94
|
-
log$(base: DecimalLike, digits?: bigint | number): this;
|
|
95
|
-
log(base: DecimalLike, digits?: bigint | number): Decimal;
|
|
96
|
-
order(): bigint;
|
|
97
|
-
|
|
98
|
-
// Conversion
|
|
99
|
-
toString(): string;
|
|
100
|
-
toFixed(fractionDigits: bigint | number): string;
|
|
101
|
-
|
|
102
|
-
number(): number;
|
|
103
|
-
integer(): bigint;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function ensureInteger(value: bigint | number, message = 'Digits must be an integer'): bigint {
|
|
107
|
-
if (typeof value === 'number') {
|
|
108
|
-
if (!Number.isFinite(value) || !Number.isInteger(value)) throw new Error(message);
|
|
109
|
-
return BigInt(value);
|
|
110
|
-
}
|
|
111
|
-
return value;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function ensureDigits(value: bigint | number): bigint {
|
|
115
|
-
const result = ensureInteger(value);
|
|
116
|
-
if (result < 0n) return 0n;
|
|
117
|
-
return result;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function pow5n(n: bigint | number): bigint {
|
|
121
|
-
const nn = Number(n);
|
|
122
|
-
if (nn < pow5nCache.length) return pow5nCache[nn];
|
|
123
|
-
return 5n ** BigInt(n);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function pow10n(n: bigint | number): bigint {
|
|
127
|
-
const nn = Number(n);
|
|
128
|
-
if (nn < pow10nCache.length) return pow10nCache[nn];
|
|
129
|
-
return 10n ** BigInt(n);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function powInt(base: Decimal, exponentInput: bigint | number, digits?: bigint | number): Decimal {
|
|
133
|
-
let exponent = BigInt(exponentInput);
|
|
134
|
-
if (exponent <= 0n) return new DecimalImpl(1n);
|
|
135
|
-
const result = new DecimalImpl(1n);
|
|
136
|
-
const factor = base.clone();
|
|
137
|
-
while (true) {
|
|
138
|
-
if (exponent & 1n) result.mul$(factor, digits);
|
|
139
|
-
exponent >>= 1n;
|
|
140
|
-
if (exponent === 0n) break;
|
|
141
|
-
factor.mul$(factor, digits);
|
|
142
|
-
}
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function powFrac(base: Decimal, fractional: Decimal, digits: bigint, digitsCount: number): Decimal {
|
|
147
|
-
if (fractional.isZero() || digitsCount <= 0) return new DecimalImpl(1n);
|
|
148
|
-
const { guardPrec, rootPrec } = estimatePowFractionalSettings(digits, digitsCount);
|
|
149
|
-
const digitsString = abs(fractional.coeff).toString().padStart(digitsCount, '0');
|
|
150
|
-
let progressiveRoot = base.round(rootPrec);
|
|
151
|
-
const result = new DecimalImpl(1n);
|
|
152
|
-
for (let i = 0; i < digitsCount; i++) {
|
|
153
|
-
progressiveRoot = progressiveRoot.root(10n, rootPrec);
|
|
154
|
-
const digit = digitsString.charCodeAt(i) - 48;
|
|
155
|
-
if (digit <= 0) continue;
|
|
156
|
-
const factor = powInt(progressiveRoot.clone(), digit, guardPrec);
|
|
157
|
-
result.mul$(factor, guardPrec);
|
|
158
|
-
}
|
|
159
|
-
return result.round$(digits);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function abs(value: bigint): bigint {
|
|
163
|
-
return value < 0n ? -value : value;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function parsePlainDecimal(input: string): { coeff: bigint; digits: bigint } {
|
|
167
|
-
if (input === '') throw new Error('Invalid number');
|
|
168
|
-
let sign = 1n;
|
|
169
|
-
let str = input;
|
|
170
|
-
if (str[0] === '-') {
|
|
171
|
-
sign = -1n;
|
|
172
|
-
str = str.slice(1);
|
|
173
|
-
} else if (str[0] === '+') {
|
|
174
|
-
str = str.slice(1);
|
|
175
|
-
}
|
|
176
|
-
if (str === '') throw new Error('Invalid number');
|
|
177
|
-
const dotIndex = str.indexOf('.');
|
|
178
|
-
let intPart = str;
|
|
179
|
-
let fracPart = '';
|
|
180
|
-
if (dotIndex >= 0) {
|
|
181
|
-
intPart = str.slice(0, dotIndex);
|
|
182
|
-
fracPart = str.slice(dotIndex + 1);
|
|
183
|
-
}
|
|
184
|
-
if (intPart === '') intPart = '0';
|
|
185
|
-
const combined = (intPart + fracPart).replace(/^0+/, '');
|
|
186
|
-
const coeffStr = combined === '' ? '0' : combined;
|
|
187
|
-
const coeff = sign * BigInt(coeffStr);
|
|
188
|
-
const digits = BigInt(fracPart.length);
|
|
189
|
-
return { coeff, digits };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function parseDecimalString(value: string): { coeff: bigint; digits: bigint } {
|
|
193
|
-
const exponentIndex = value.search(/[eE]/);
|
|
194
|
-
if (exponentIndex === -1) return parsePlainDecimal(value);
|
|
195
|
-
const basePart = value.slice(0, exponentIndex);
|
|
196
|
-
const exponentPart = value.slice(exponentIndex + 1);
|
|
197
|
-
if (exponentPart.trim() === '') throw new Error('Invalid number');
|
|
198
|
-
const { coeff, digits } = parsePlainDecimal(basePart);
|
|
199
|
-
const adjustment = BigInt(exponentPart);
|
|
200
|
-
return { coeff, digits: digits - adjustment };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function alignForOperation(a: Decimal, b: Decimal) {
|
|
204
|
-
const digits = a.digits > b.digits ? a.digits : b.digits;
|
|
205
|
-
const aDiff = digits - a.digits;
|
|
206
|
-
const bDiff = digits - b.digits;
|
|
207
|
-
return {
|
|
208
|
-
digits,
|
|
209
|
-
aCoeff: a.coeff * pow10n(aDiff),
|
|
210
|
-
bCoeff: b.coeff * pow10n(bDiff),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function toBoundedNonNegativeNumber(value: bigint): number {
|
|
215
|
-
const numeric = Math.max(0, Number(value));
|
|
216
|
-
return Math.min(Number.MAX_SAFE_INTEGER, numeric);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function estimateLogGuardSettings(target: number, baseDigits: bigint, valueDigits: bigint) {
|
|
220
|
-
const baseScale = toBoundedNonNegativeNumber(baseDigits);
|
|
221
|
-
const valueScale = toBoundedNonNegativeNumber(valueDigits);
|
|
222
|
-
const minGuard = Math.max(baseScale, valueScale) + 1;
|
|
223
|
-
let guardPrec = Math.max(minGuard, 1);
|
|
224
|
-
let fracPrec = target + guardPrec;
|
|
225
|
-
while (true) {
|
|
226
|
-
const bits = Math.ceil(fracPrec * LOG_BINARY_PER_DECIMAL) + guardPrec;
|
|
227
|
-
const ops = Math.max(bits * 2, 1);
|
|
228
|
-
const required = Math.max(minGuard, Math.ceil(Math.log10(ops)) + 1);
|
|
229
|
-
if (required <= guardPrec) {
|
|
230
|
-
const divPrec = fracPrec + guardPrec;
|
|
231
|
-
return { guardPrec, fracPrec, bits, divPrec };
|
|
232
|
-
}
|
|
233
|
-
guardPrec = required;
|
|
234
|
-
fracPrec = target + guardPrec;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function estimatePowFractionalSettings(target: bigint, digitsCount: number) {
|
|
239
|
-
const count = Math.max(1, digitsCount);
|
|
240
|
-
const guardExtra = Math.max(6, Math.ceil(Math.log10(count * 4)) + 2);
|
|
241
|
-
const guardPrec = target + BigInt(guardExtra);
|
|
242
|
-
const rootPrec = guardPrec + BigInt(Math.max(guardExtra, 6));
|
|
243
|
-
return { guardPrec, rootPrec };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function estimateRootIterSettings(target: bigint, degree: bigint) {
|
|
247
|
-
const degreeDigits = Math.max(1, degree.toString().replace('-', '').length);
|
|
248
|
-
const extra = Math.max(12, degreeDigits + 4);
|
|
249
|
-
const iterPrec = target + BigInt(extra);
|
|
250
|
-
const stopShift = target + 2n;
|
|
251
|
-
return { iterPrec, stopShift };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function extractLogIntegerPositive(
|
|
255
|
-
value: Decimal,
|
|
256
|
-
base: Decimal,
|
|
257
|
-
digits: number,
|
|
258
|
-
guardPrec: number,
|
|
259
|
-
): {
|
|
260
|
-
exponent: bigint;
|
|
261
|
-
remainder: Decimal;
|
|
262
|
-
} {
|
|
263
|
-
const powers: Decimal[] = [];
|
|
264
|
-
const exponents: bigint[] = [];
|
|
265
|
-
let power = new DecimalImpl(base);
|
|
266
|
-
let exponent = 1n;
|
|
267
|
-
const divDigits = digits + Math.max(guardPrec, 1);
|
|
268
|
-
while (power.le(value)) {
|
|
269
|
-
powers.push(power);
|
|
270
|
-
exponents.push(exponent);
|
|
271
|
-
power = power.mul(power);
|
|
272
|
-
exponent *= 2n;
|
|
273
|
-
}
|
|
274
|
-
const remainder = new DecimalImpl(value);
|
|
275
|
-
let result = 0n;
|
|
276
|
-
for (let i = powers.length - 1; i >= 0; i--) {
|
|
277
|
-
const candidate = powers[i];
|
|
278
|
-
if (remainder.ge(candidate)) {
|
|
279
|
-
remainder.div$(candidate, divDigits);
|
|
280
|
-
result += exponents[i];
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return { exponent: result, remainder };
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function extractLogIntegerAndNormalize(
|
|
287
|
-
value: Decimal,
|
|
288
|
-
base: Decimal,
|
|
289
|
-
digits: number,
|
|
290
|
-
guardPrec: number,
|
|
291
|
-
): {
|
|
292
|
-
exponent: bigint;
|
|
293
|
-
remainder: Decimal;
|
|
294
|
-
} {
|
|
295
|
-
const divDigits = digits + Math.max(guardPrec, 1);
|
|
296
|
-
if (value.eq(DECIMAL_ONE)) return { exponent: 0n, remainder: DECIMAL_ONE.clone() };
|
|
297
|
-
if (value.ge(DECIMAL_ONE)) return extractLogIntegerPositive(value, base, digits, guardPrec);
|
|
298
|
-
const positive = extractLogIntegerPositive(DECIMAL_ONE.div(value, divDigits), base, digits, guardPrec);
|
|
299
|
-
if (positive.remainder.eq(DECIMAL_ONE)) {
|
|
300
|
-
return { exponent: -positive.exponent, remainder: DECIMAL_ONE.clone() };
|
|
301
|
-
}
|
|
302
|
-
return {
|
|
303
|
-
exponent: -positive.exponent - 1n,
|
|
304
|
-
remainder: DECIMAL_ONE.div(positive.remainder, divDigits).mul$(base),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
class DecimalImpl implements Decimal {
|
|
309
|
-
declare readonly [__brand]: never;
|
|
310
|
-
|
|
311
|
-
public coeff: bigint;
|
|
312
|
-
public digits: bigint;
|
|
313
|
-
|
|
314
|
-
constructor(v: DecimalLike, digitsOverride?: bigint) {
|
|
315
|
-
if (typeof v === 'number') {
|
|
316
|
-
if (v !== v || v === Infinity || v === -Infinity) throw new Error('Invalid number');
|
|
317
|
-
({ coeff: this.coeff, digits: this.digits } = parseDecimalString(v.toString()));
|
|
318
|
-
} else if (typeof v === 'string') {
|
|
319
|
-
const trimmed = v.trim();
|
|
320
|
-
if (trimmed === '') throw new Error('Invalid number');
|
|
321
|
-
({ coeff: this.coeff, digits: this.digits } = parseDecimalString(trimmed));
|
|
322
|
-
} else if (typeof v === 'bigint') {
|
|
323
|
-
this.coeff = v;
|
|
324
|
-
this.digits = digitsOverride ?? 0n;
|
|
325
|
-
} else if (
|
|
326
|
-
typeof v === 'object' &&
|
|
327
|
-
v !== null &&
|
|
328
|
-
'coeff' in v &&
|
|
329
|
-
typeof v.coeff === 'bigint' &&
|
|
330
|
-
'digits' in v &&
|
|
331
|
-
typeof v.digits === 'bigint'
|
|
332
|
-
) {
|
|
333
|
-
this.coeff = v.coeff;
|
|
334
|
-
this.digits = v.digits;
|
|
335
|
-
} else {
|
|
336
|
-
throw new Error('Invalid input type for Decimal');
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
clone(): DecimalImpl {
|
|
341
|
-
return new DecimalImpl({ coeff: this.coeff, digits: this.digits });
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
#set$(coeff: bigint | Decimal, digits: bigint = 0n): this {
|
|
345
|
-
if (isDecimal(coeff)) {
|
|
346
|
-
digits = coeff.digits;
|
|
347
|
-
coeff = coeff.coeff;
|
|
348
|
-
}
|
|
349
|
-
this.coeff = coeff;
|
|
350
|
-
this.digits = digits;
|
|
351
|
-
return this;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
#div$(divisor: Decimal, targetDigits: bigint, mode: RoundingMode): this {
|
|
355
|
-
let numerator = this.coeff;
|
|
356
|
-
let denominator = divisor.coeff;
|
|
357
|
-
|
|
358
|
-
const shift = divisor.digits + targetDigits - this.digits;
|
|
359
|
-
if (shift >= 0n) {
|
|
360
|
-
numerator *= pow10n(shift);
|
|
361
|
-
} else {
|
|
362
|
-
denominator *= pow10n(-shift);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
let quotient = numerator / denominator;
|
|
366
|
-
const remainder = numerator - quotient * denominator;
|
|
367
|
-
if (remainder !== 0n && mode !== 'trunc') {
|
|
368
|
-
const positive = numerator >= 0n;
|
|
369
|
-
switch (mode) {
|
|
370
|
-
case 'floor':
|
|
371
|
-
if (!positive) quotient -= 1n;
|
|
372
|
-
break;
|
|
373
|
-
case 'ceil':
|
|
374
|
-
if (positive) quotient += 1n;
|
|
375
|
-
break;
|
|
376
|
-
case 'round': {
|
|
377
|
-
const absRemainder = abs(remainder);
|
|
378
|
-
const threshold = denominator < 0n ? -denominator : denominator;
|
|
379
|
-
if (absRemainder * 2n >= threshold) {
|
|
380
|
-
quotient += positive ? 1n : -1n;
|
|
381
|
-
}
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
default:
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return this.#set$(quotient, targetDigits);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
#rescale$(targetDigits: bigint | number, mode: RoundingMode = 'trunc'): this {
|
|
393
|
-
const normalized = ensureInteger(targetDigits);
|
|
394
|
-
if (this.isZero()) {
|
|
395
|
-
this.digits = normalized;
|
|
396
|
-
return this;
|
|
397
|
-
}
|
|
398
|
-
if (normalized === this.digits) return this;
|
|
399
|
-
if (normalized > this.digits) {
|
|
400
|
-
const scale = pow10n(normalized - this.digits);
|
|
401
|
-
this.coeff *= scale;
|
|
402
|
-
this.digits = normalized;
|
|
403
|
-
return this;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return this.#div$(DECIMAL_ONE, normalized, mode);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
#stripTrailingZeros$(): this {
|
|
410
|
-
if (this.coeff === 0n) {
|
|
411
|
-
this.digits = 0n;
|
|
412
|
-
return this;
|
|
413
|
-
}
|
|
414
|
-
if (this.digits <= 0n) return this;
|
|
415
|
-
while (this.digits > 0n && this.coeff % 10n === 0n) {
|
|
416
|
-
this.coeff /= 10n;
|
|
417
|
-
this.digits -= 1n;
|
|
418
|
-
}
|
|
419
|
-
return this;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
round$(digits: bigint | number = 0, force = false): this {
|
|
423
|
-
const normalized = ensureInteger(digits);
|
|
424
|
-
if (!force && this.digits <= normalized) return this;
|
|
425
|
-
return this.#rescale$(normalized, 'round');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
round(digits: bigint | number = 0, force = false): DecimalImpl {
|
|
429
|
-
return this.clone().round$(digits, force);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
roundBy$(step: DecimalLike, mode: RoundingMode = 'round'): this {
|
|
433
|
-
const multiple = new DecimalImpl(step).abs();
|
|
434
|
-
if (multiple.isZero()) throw new Error('Cannot align to zero');
|
|
435
|
-
|
|
436
|
-
return this.div$(multiple, 0n, mode).mul$(multiple);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
roundBy(step: DecimalLike, mode: RoundingMode = 'round'): DecimalImpl {
|
|
440
|
-
return this.clone().roundBy$(step, mode);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
floor$(digits: bigint | number = 0, force = false): this {
|
|
444
|
-
const normalized = ensureInteger(digits);
|
|
445
|
-
if (!force && this.digits <= normalized) return this;
|
|
446
|
-
return this.#rescale$(normalized, 'floor');
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
floor(digits: bigint | number = 0, force = false): DecimalImpl {
|
|
450
|
-
return this.clone().floor$(digits, force);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
floorBy$(step: DecimalLike): this {
|
|
454
|
-
return this.roundBy$(step, 'floor');
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
floorBy(step: DecimalLike): DecimalImpl {
|
|
458
|
-
return this.clone().floorBy$(step);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
ceil$(digits: bigint | number = 0, force = false): this {
|
|
462
|
-
const normalized = ensureInteger(digits);
|
|
463
|
-
if (!force && this.digits <= normalized) return this;
|
|
464
|
-
return this.#rescale$(normalized, 'ceil');
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
ceil(digits: bigint | number = 0, force = false): DecimalImpl {
|
|
468
|
-
return this.clone().ceil$(digits, force);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
ceilBy$(step: DecimalLike): this {
|
|
472
|
-
return this.roundBy$(step, 'ceil');
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
ceilBy(step: DecimalLike): DecimalImpl {
|
|
476
|
-
return this.clone().ceilBy$(step);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
trunc$(digits: bigint | number = 0, force = false): this {
|
|
480
|
-
const normalized = ensureInteger(digits);
|
|
481
|
-
if (!force && this.digits <= normalized) return this;
|
|
482
|
-
return this.#rescale$(normalized, 'trunc');
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
trunc(digits: bigint | number = 0, force = false): DecimalImpl {
|
|
486
|
-
return this.clone().trunc$(digits, force);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
rescale$(digits?: bigint | number, mode: RoundingMode = 'trunc'): this {
|
|
490
|
-
if (digits == null) return this.#stripTrailingZeros$();
|
|
491
|
-
return this.#rescale$(digits, mode);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
rescale(digits?: bigint | number, mode: RoundingMode = 'trunc'): DecimalImpl {
|
|
495
|
-
return this.clone().rescale$(digits, mode);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
truncBy$(step: DecimalLike): this {
|
|
499
|
-
return this.roundBy$(step, 'trunc');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
truncBy(step: DecimalLike): DecimalImpl {
|
|
503
|
-
return this.clone().truncBy$(step);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
#splitWith(apply: (value: DecimalImpl) => void): [DecimalImpl, DecimalImpl] {
|
|
507
|
-
const original = this.clone();
|
|
508
|
-
apply(this);
|
|
509
|
-
return [this, original.sub$(this)];
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
split$(digits?: bigint | number, mode: RoundingMode = 'floor'): [DecimalImpl, DecimalImpl] {
|
|
513
|
-
return this.#splitWith((value) => value.#rescale$(digits ?? 0n, mode));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
split(digits?: bigint | number, mode: RoundingMode = 'floor'): [Decimal, Decimal] {
|
|
517
|
-
return this.clone().split$(digits, mode);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
splitBy$(step: DecimalLike, mode: RoundingMode = 'floor'): [DecimalImpl, DecimalImpl] {
|
|
521
|
-
return this.#splitWith((value) => value.roundBy$(step, mode));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
splitBy(step: DecimalLike, mode: RoundingMode = 'floor'): [Decimal, Decimal] {
|
|
525
|
-
return this.clone().splitBy$(step, mode);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
neg$(flag?: boolean): this {
|
|
529
|
-
if (flag !== false) {
|
|
530
|
-
this.coeff = -this.coeff;
|
|
531
|
-
}
|
|
532
|
-
return this;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
neg(flag?: boolean): DecimalImpl {
|
|
536
|
-
return this.clone().neg$(flag);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
isZero(): boolean {
|
|
540
|
-
return this.coeff === 0n;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
isPositive(): boolean {
|
|
544
|
-
return this.coeff > 0n;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
isNegative(): boolean {
|
|
548
|
-
return this.coeff < 0n;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
add$(v: DecimalLike): this {
|
|
552
|
-
const value = Decimal(v);
|
|
553
|
-
const { digits, aCoeff, bCoeff } = alignForOperation(this, value);
|
|
554
|
-
this.coeff = aCoeff + bCoeff;
|
|
555
|
-
this.digits = digits;
|
|
556
|
-
return this;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
add(v: DecimalLike): DecimalImpl {
|
|
560
|
-
return this.clone().add$(v);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
sub$(v: DecimalLike): this {
|
|
564
|
-
const value = Decimal(v);
|
|
565
|
-
const { digits, aCoeff, bCoeff } = alignForOperation(this, value);
|
|
566
|
-
this.coeff = aCoeff - bCoeff;
|
|
567
|
-
this.digits = digits;
|
|
568
|
-
return this;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
sub(v: DecimalLike): DecimalImpl {
|
|
572
|
-
return this.clone().sub$(v);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
mul$(v: DecimalLike, digits?: number | bigint | undefined): this {
|
|
576
|
-
const value = Decimal(v);
|
|
577
|
-
this.coeff *= value.coeff;
|
|
578
|
-
this.digits += value.digits;
|
|
579
|
-
if (digits !== undefined) this.round$(digits);
|
|
580
|
-
return this;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
mul(v: DecimalLike, digits?: number | bigint | undefined): DecimalImpl {
|
|
584
|
-
return this.clone().mul$(v, digits);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
shift10$(exponent: bigint | number): this {
|
|
588
|
-
const normalized = ensureInteger(exponent, 'Shift amount must be an integer');
|
|
589
|
-
if (normalized === 0n) return this;
|
|
590
|
-
this.digits -= normalized;
|
|
591
|
-
return this;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
shift10(exponent: bigint | number): DecimalImpl {
|
|
595
|
-
return this.clone().shift10$(exponent);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
inverse$(digits: bigint | number = DEFAULT_DIVISION_PRECISION): this {
|
|
599
|
-
if (this.isZero()) throw new Error('Division by zero');
|
|
600
|
-
const target = ensureDigits(digits);
|
|
601
|
-
const divisor = this.clone();
|
|
602
|
-
return this.#set$(1n).div$(divisor, target);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
inverse(digits: bigint | number = DEFAULT_DIVISION_PRECISION): DecimalImpl {
|
|
606
|
-
return this.clone().inverse$(digits);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
div$(v: DecimalLike, digits?: bigint | number, mode: RoundingMode = 'round'): this {
|
|
610
|
-
const stripTrailingZeros = digits === undefined;
|
|
611
|
-
const targetDigits = ensureDigits(digits ?? DEFAULT_DIVISION_PRECISION);
|
|
612
|
-
const divisor = Decimal(v);
|
|
613
|
-
if (divisor.isZero()) throw new Error('Division by zero');
|
|
614
|
-
if (this.isZero()) return this;
|
|
615
|
-
|
|
616
|
-
this.#div$(divisor, targetDigits, mode);
|
|
617
|
-
return stripTrailingZeros ? this.#stripTrailingZeros$() : this;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
div(v: DecimalLike, digits?: bigint | number, mode: RoundingMode = 'round'): DecimalImpl {
|
|
621
|
-
return this.clone().div$(v, digits, mode);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
abs$(): this {
|
|
625
|
-
if (this.isNegative()) this.coeff = -this.coeff;
|
|
626
|
-
return this;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
abs(): DecimalImpl {
|
|
630
|
-
return this.clone().abs$();
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
mod$(v: DecimalLike): this {
|
|
634
|
-
const value = Decimal(v);
|
|
635
|
-
if (value.isZero()) throw new Error('Division by zero');
|
|
636
|
-
const { digits, aCoeff, bCoeff } = alignForOperation(this, value);
|
|
637
|
-
this.coeff = aCoeff % bCoeff;
|
|
638
|
-
this.digits = digits;
|
|
639
|
-
return this;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
mod(v: DecimalLike): DecimalImpl {
|
|
643
|
-
return this.clone().mod$(v);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
modPositive$(v: DecimalLike): this {
|
|
647
|
-
const divisor = Decimal(v);
|
|
648
|
-
if (divisor.isNegative()) throw new Error('Modulo divisor must be positive');
|
|
649
|
-
this.mod$(divisor);
|
|
650
|
-
if (this.isNegative()) this.add$(divisor);
|
|
651
|
-
return this;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
modPositive(v: DecimalLike): DecimalImpl {
|
|
655
|
-
return this.clone().modPositive$(v);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
clamp$(minValue: DecimalLike | undefined, maxValue: DecimalLike | undefined): this {
|
|
659
|
-
const lower = Decimal(minValue);
|
|
660
|
-
const upper = Decimal(maxValue);
|
|
661
|
-
if (lower && upper && lower.gt(upper)) throw new Error('Invalid clamp range');
|
|
662
|
-
if (lower && this.lt(lower)) {
|
|
663
|
-
this.coeff = lower.coeff;
|
|
664
|
-
this.digits = lower.digits;
|
|
665
|
-
return this;
|
|
666
|
-
}
|
|
667
|
-
if (upper && this.gt(upper)) {
|
|
668
|
-
this.coeff = upper.coeff;
|
|
669
|
-
this.digits = upper.digits;
|
|
670
|
-
return this;
|
|
671
|
-
}
|
|
672
|
-
return this;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
clamp(minValue: DecimalLike | undefined, maxValue: DecimalLike | undefined): DecimalImpl {
|
|
676
|
-
return this.clone().clamp$(minValue, maxValue);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
cmp(v: DecimalLike): number {
|
|
680
|
-
const other = Decimal(v);
|
|
681
|
-
const { aCoeff, bCoeff } = alignForOperation(this, other);
|
|
682
|
-
if (aCoeff === bCoeff) return 0;
|
|
683
|
-
return aCoeff > bCoeff ? 1 : -1;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
eq(v: DecimalLike): boolean {
|
|
687
|
-
return this.cmp(v) === 0;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
neq(v: DecimalLike): boolean {
|
|
691
|
-
return this.cmp(v) !== 0;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
lt(v: DecimalLike): boolean {
|
|
695
|
-
return this.cmp(v) < 0;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
gt(v: DecimalLike): boolean {
|
|
699
|
-
return this.cmp(v) > 0;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
le(v: DecimalLike): boolean {
|
|
703
|
-
return this.cmp(v) <= 0;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
ge(v: DecimalLike): boolean {
|
|
707
|
-
return this.cmp(v) >= 0;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
between(minValue: DecimalLike | undefined, maxValue: DecimalLike | undefined): boolean {
|
|
711
|
-
const lower = Decimal(minValue);
|
|
712
|
-
const upper = Decimal(maxValue);
|
|
713
|
-
if (lower && upper && lower.gt(upper)) throw new Error('Invalid between range');
|
|
714
|
-
if (lower && this.lt(lower)) return false;
|
|
715
|
-
if (upper && this.gt(upper)) return false;
|
|
716
|
-
return true;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
isCloseTo(v: DecimalLike, tolerance: DecimalLike): boolean {
|
|
720
|
-
const toleranceValue = Decimal(tolerance);
|
|
721
|
-
if (toleranceValue.isNegative()) throw new Error('Tolerance must be non-negative');
|
|
722
|
-
|
|
723
|
-
return this.sub(v).abs$().le(toleranceValue);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
pow$(exponent: DecimalLike, digits: bigint | number = DEFAULT_DIVISION_PRECISION): this {
|
|
727
|
-
const prec = ensureDigits(digits);
|
|
728
|
-
const expVal = Decimal(exponent);
|
|
729
|
-
if (expVal.isZero()) return this.#set$(1n);
|
|
730
|
-
if (this.isZero()) {
|
|
731
|
-
if (expVal.isNegative()) throw new Error('Zero to negative exponent is undefined');
|
|
732
|
-
return this;
|
|
733
|
-
}
|
|
734
|
-
const negExp = expVal.isNegative();
|
|
735
|
-
const [intPart, fracPart] = expVal.abs().split$();
|
|
736
|
-
const base = this.clone();
|
|
737
|
-
const fracScale = fracPart.isZero() ? 0 : Number(fracPart.digits);
|
|
738
|
-
if (base.isNegative() && fracScale > 0) throw new Error('Fractional exponent requires non-negative base');
|
|
739
|
-
|
|
740
|
-
const result = powInt(base, intPart.coeff);
|
|
741
|
-
let fracPrec = prec;
|
|
742
|
-
if (fracScale > 0) {
|
|
743
|
-
const fracPad = BigInt(Math.max(4, fracScale));
|
|
744
|
-
fracPrec = prec + fracPad;
|
|
745
|
-
}
|
|
746
|
-
const fracFactor = powFrac(base, fracPart, fracPrec, fracScale);
|
|
747
|
-
if (fracScale > 0) {
|
|
748
|
-
result.mul$(fracFactor, fracPrec);
|
|
749
|
-
}
|
|
750
|
-
const workPrec = fracPrec > prec ? fracPrec : prec;
|
|
751
|
-
const outPrec = fracScale > 0 ? fracPrec : prec;
|
|
752
|
-
|
|
753
|
-
if (negExp) {
|
|
754
|
-
this.#set$(1n).div$(result, workPrec);
|
|
755
|
-
} else {
|
|
756
|
-
result.round$(workPrec, true);
|
|
757
|
-
this.#set$(result);
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
this.round$(outPrec, outPrec === 0n);
|
|
761
|
-
return this;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
pow(exponent: DecimalLike, digits: bigint | number = DEFAULT_DIVISION_PRECISION): DecimalImpl {
|
|
765
|
-
return this.clone().pow$(exponent, digits);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
root$(degreeInput: bigint | number, digits: bigint | number = DEFAULT_DIVISION_PRECISION): this {
|
|
769
|
-
const degree = ensureInteger(degreeInput, 'Root degree must be an integer');
|
|
770
|
-
if (degree <= 0n) throw new Error('Invalid root degree');
|
|
771
|
-
const prec = ensureDigits(digits);
|
|
772
|
-
if (degree === 1n) {
|
|
773
|
-
return prec < this.digits ? this : this.trunc$(prec, true);
|
|
774
|
-
}
|
|
775
|
-
if (this.isZero()) return this;
|
|
776
|
-
|
|
777
|
-
const wasNegative = this.isNegative();
|
|
778
|
-
if (wasNegative && degree % 2n === 0n) throw new Error('Even root of negative value is not defined');
|
|
779
|
-
|
|
780
|
-
const magnitude = this.abs();
|
|
781
|
-
const degMinusOne = degree - 1n;
|
|
782
|
-
const { iterPrec, stopShift } = estimateRootIterSettings(prec, degree);
|
|
783
|
-
const tolerance = pow10(-stopShift);
|
|
784
|
-
|
|
785
|
-
const initial = (() => {
|
|
786
|
-
const approx = magnitude.number();
|
|
787
|
-
if (Number.isFinite(approx) && approx > 0) {
|
|
788
|
-
const degAsNumber = Number(degree);
|
|
789
|
-
if (degAsNumber > 0) {
|
|
790
|
-
const rootApprox = Math.pow(approx, 1 / degAsNumber);
|
|
791
|
-
if (Number.isFinite(rootApprox) && rootApprox > 0) {
|
|
792
|
-
const guess = new DecimalImpl(rootApprox);
|
|
793
|
-
return guess.round$(iterPrec, true);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
const orderEstimate = magnitude.order();
|
|
798
|
-
return new DecimalImpl(pow10(orderEstimate / degree)).round$(iterPrec, true);
|
|
799
|
-
})();
|
|
800
|
-
|
|
801
|
-
this.#set$(initial.coeff, initial.digits);
|
|
802
|
-
if (this.isZero()) this.#set$(1n);
|
|
803
|
-
|
|
804
|
-
for (let i = 0; i < 64; i++) {
|
|
805
|
-
const power = powInt(this.clone(), degMinusOne, iterPrec);
|
|
806
|
-
if (power.isZero()) break;
|
|
807
|
-
const term = magnitude.div(power, iterPrec);
|
|
808
|
-
const next = this.mul(degMinusOne, iterPrec).add$(term).div$(degree, iterPrec);
|
|
809
|
-
if (next.isCloseTo(this, tolerance) || next.eq(this)) {
|
|
810
|
-
this.#set$(next);
|
|
811
|
-
break;
|
|
812
|
-
}
|
|
813
|
-
this.#set$(next);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
this.round$(iterPrec, true);
|
|
817
|
-
this.round$(prec, prec === 0n);
|
|
818
|
-
if (wasNegative) this.neg$();
|
|
819
|
-
return this;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
root(degree: bigint | number, digits: bigint | number = DEFAULT_DIVISION_PRECISION): DecimalImpl {
|
|
823
|
-
return this.clone().root$(degree, digits);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
sqrt$(digits: bigint | number = DEFAULT_DIVISION_PRECISION): this {
|
|
827
|
-
return this.root$(2n, digits);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
sqrt(digits: bigint | number = DEFAULT_DIVISION_PRECISION): DecimalImpl {
|
|
831
|
-
return this.clone().sqrt$(digits);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
log$(base: DecimalLike, digits: bigint | number = DEFAULT_DIVISION_PRECISION): this {
|
|
835
|
-
const precision = Number(ensureDigits(digits));
|
|
836
|
-
const baseValue = new DecimalImpl(base);
|
|
837
|
-
|
|
838
|
-
if (!this.isPositive()) throw new Error('Logarithm argument must be positive');
|
|
839
|
-
if (!baseValue.isPositive()) throw new Error('Logarithm base must be positive');
|
|
840
|
-
if (baseValue.eq(DECIMAL_ONE)) throw new Error('Logarithm base cannot be one');
|
|
841
|
-
|
|
842
|
-
const { guardPrec, fracPrec, bits, divPrec } = estimateLogGuardSettings(precision, baseValue.digits, this.digits);
|
|
843
|
-
|
|
844
|
-
const baseBelowOne = baseValue.lt(DECIMAL_ONE);
|
|
845
|
-
const baseNorm = baseBelowOne ? DECIMAL_ONE.div(baseValue, divPrec) : baseValue;
|
|
846
|
-
|
|
847
|
-
const { exponent: intExp, remainder } = extractLogIntegerAndNormalize(this, baseNorm, fracPrec, guardPrec);
|
|
848
|
-
|
|
849
|
-
this.#set$(intExp);
|
|
850
|
-
if (fracPrec > 0) {
|
|
851
|
-
let flags = 0n;
|
|
852
|
-
for (let i = 0; i < bits; i++) {
|
|
853
|
-
remainder.mul$(remainder, divPrec);
|
|
854
|
-
flags <<= 1n;
|
|
855
|
-
if (remainder.ge(baseNorm)) {
|
|
856
|
-
remainder.div$(baseNorm, divPrec);
|
|
857
|
-
flags |= 1n;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
if (flags !== 0n) {
|
|
861
|
-
this.add$({ coeff: flags * pow5n(bits), digits: BigInt(bits) });
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const finalPrec = precision === 0 ? 0 : fracPrec;
|
|
866
|
-
this.round$(finalPrec);
|
|
867
|
-
if (baseBelowOne) this.neg$();
|
|
868
|
-
return this;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
log(base: DecimalLike, digits: bigint | number = DEFAULT_DIVISION_PRECISION): DecimalImpl {
|
|
872
|
-
return this.clone().log$(base, digits);
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
sign$() {
|
|
876
|
-
if (this.isZero()) return this;
|
|
877
|
-
this.coeff = this.coeff < 0n ? -1n : 1n;
|
|
878
|
-
this.digits = 0n;
|
|
879
|
-
return this;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
sign() {
|
|
883
|
-
return this.clone().sign$();
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
order(): bigint {
|
|
887
|
-
if (this.isZero()) throw new RangeError('order undefined for 0');
|
|
888
|
-
return BigInt(abs(this.coeff).toString().length) - 1n - this.digits;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
toFixed(fractionDigits: bigint | number): string {
|
|
892
|
-
const errorMessage = 'Fraction digits must be a non-negative integer';
|
|
893
|
-
const digits = ensureInteger(fractionDigits, errorMessage);
|
|
894
|
-
if (digits < 0n) throw new Error(errorMessage);
|
|
895
|
-
return this.round(digits, true).toString();
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
toString(): string {
|
|
899
|
-
if (this.coeff === 0n) {
|
|
900
|
-
if (this.digits <= 0n) return '0';
|
|
901
|
-
return `0.${'0'.repeat(Number(this.digits))}`;
|
|
902
|
-
}
|
|
903
|
-
const negative = this.coeff < 0n;
|
|
904
|
-
const sign = negative ? '-' : '';
|
|
905
|
-
const coeffDigits = (negative ? -this.coeff : this.coeff).toString();
|
|
906
|
-
if (this.digits <= 0n) {
|
|
907
|
-
return `${sign}${coeffDigits}${'0'.repeat(Number(-this.digits))}`;
|
|
908
|
-
}
|
|
909
|
-
const decimals = Number(this.digits);
|
|
910
|
-
const padded = coeffDigits.padStart(decimals + 1, '0');
|
|
911
|
-
const split = padded.length - decimals;
|
|
912
|
-
const integerPart = padded.slice(0, split);
|
|
913
|
-
const fractionPart = padded.slice(split).padEnd(decimals, '0');
|
|
914
|
-
return `${sign}${integerPart}.${fractionPart}`;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
[Symbol.for('nodejs.util.inspect.custom')](_depth: number, options: object) {
|
|
918
|
-
if ('colors' in options && options?.colors) {
|
|
919
|
-
return `\x1b[33m${this.toString()}\x1b[m \x1b[90m(${this.coeff} * 10 ** ${-this.digits})\x1b[m`;
|
|
920
|
-
}
|
|
921
|
-
return this.toString();
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
number(): number {
|
|
925
|
-
return Number(this.toString());
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
integer(): bigint {
|
|
929
|
-
if (this.digits <= 0n) {
|
|
930
|
-
return this.coeff * pow10n(-this.digits);
|
|
931
|
-
}
|
|
932
|
-
return this.coeff / pow10n(this.digits);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
const DEFAULT_DIVISION_PRECISION = 18n;
|
|
937
|
-
const LOG_BINARY_PER_DECIMAL = Math.log2(10);
|
|
938
|
-
|
|
939
|
-
const DECIMAL_ONE = new DecimalImpl(1n);
|
|
940
|
-
|
|
941
|
-
// create pow10n cache
|
|
942
|
-
const pow5nCache: bigint[] = [];
|
|
943
|
-
const pow10nCache: bigint[] = [];
|
|
944
|
-
|
|
945
|
-
(function craetePowCache() {
|
|
946
|
-
for (let i = 0; i < 256; i++) {
|
|
947
|
-
pow10nCache[i] = 10n ** BigInt(i);
|
|
948
|
-
pow5nCache[i] = 5n ** BigInt(i);
|
|
949
|
-
}
|
|
950
|
-
})();
|
|
951
|
-
|
|
952
|
-
export function Decimal(v: null): null;
|
|
953
|
-
export function Decimal(v: undefined): undefined;
|
|
954
|
-
export function Decimal(v: DecimalLike): Decimal;
|
|
955
|
-
export function Decimal(v: DecimalLike | null): Decimal | null;
|
|
956
|
-
export function Decimal(v: DecimalLike | undefined): Decimal | undefined;
|
|
957
|
-
export function Decimal(v: DecimalLike | undefined | null): Decimal | undefined | null;
|
|
958
|
-
export function Decimal(v: DecimalLike | undefined | null): Decimal | undefined | null {
|
|
959
|
-
if (v == null) return v;
|
|
960
|
-
if (isDecimal(v)) return v;
|
|
961
|
-
return new DecimalImpl(v) as Decimal;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
965
|
-
export namespace Decimal {
|
|
966
|
-
export function isDecimal(v: unknown): v is Decimal {
|
|
967
|
-
return v instanceof DecimalImpl;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
export function isDecimalLike(v: unknown): v is DecimalLike {
|
|
971
|
-
if (isDecimal(v)) return true;
|
|
972
|
-
if (typeof v === 'string') return true;
|
|
973
|
-
if (typeof v === 'number') return true;
|
|
974
|
-
if (typeof v === 'bigint') return true;
|
|
975
|
-
if (
|
|
976
|
-
typeof v === 'object' &&
|
|
977
|
-
v &&
|
|
978
|
-
'coeff' in v &&
|
|
979
|
-
'digits' in v &&
|
|
980
|
-
typeof v.coeff === 'bigint' &&
|
|
981
|
-
typeof v.digits === 'bigint'
|
|
982
|
-
) {
|
|
983
|
-
return true;
|
|
984
|
-
}
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
export function pow10(n: bigint | number): Decimal {
|
|
989
|
-
return new DecimalImpl({ coeff: 1n, digits: -BigInt(n) });
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
export function minmax(...values: (DecimalLike | null | undefined)[]): [Decimal | null, Decimal | null] {
|
|
993
|
-
let minValue: Decimal | null = null;
|
|
994
|
-
let maxValue: Decimal | null = null;
|
|
995
|
-
for (let i = 0; i < values.length; i++) {
|
|
996
|
-
const candidate = Decimal(values[i]);
|
|
997
|
-
if (candidate == null) continue;
|
|
998
|
-
if (minValue === null || candidate.lt(minValue)) minValue = candidate;
|
|
999
|
-
if (maxValue === null || candidate.gt(maxValue)) maxValue = candidate;
|
|
1000
|
-
}
|
|
1001
|
-
return [minValue, maxValue];
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
export function min(...values: (DecimalLike | null | undefined)[]): Decimal | null {
|
|
1005
|
-
return minmax(...values)[0];
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
export function max(...values: (DecimalLike | null | undefined)[]): Decimal | null {
|
|
1009
|
-
return minmax(...values)[1];
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
export const isDecimal = Decimal.isDecimal;
|
|
1014
|
-
export const isDecimalLike = Decimal.isDecimalLike;
|
|
1015
|
-
export const pow10 = Decimal.pow10;
|
|
1016
|
-
export const minmax = Decimal.minmax;
|
|
1017
|
-
export const min = Decimal.min;
|
|
1018
|
-
export const max = Decimal.max;
|
|
1019
|
-
|
|
1020
|
-
export default Decimal;
|