@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/tests/decimal.spec.ts
DELETED
|
@@ -1,1239 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type { RoundingMode } from '../src/index.ts';
|
|
3
|
-
import { Decimal, isDecimal, max, min, minmax, pow10 } from '../src/index.ts';
|
|
4
|
-
|
|
5
|
-
const guardAllowance = (precision: bigint, base: NonNullable<Decimal>, value: NonNullable<Decimal>) => {
|
|
6
|
-
const target = precision < 0n ? 0 : Number(precision);
|
|
7
|
-
const baseScale = base.digits <= 0n ? 0 : Number(base.digits);
|
|
8
|
-
const valueScale = value.digits <= 0n ? 0 : Number(value.digits);
|
|
9
|
-
const minGuard = Math.max(baseScale, valueScale) + 1;
|
|
10
|
-
let guard = Math.max(minGuard, 1);
|
|
11
|
-
for (;;) {
|
|
12
|
-
const frac = target + guard;
|
|
13
|
-
const steps = Math.ceil(frac * Math.log2(10)) + guard;
|
|
14
|
-
const ops = Math.max(steps * 2, 1);
|
|
15
|
-
const required = Math.max(minGuard, Math.ceil(Math.log10(ops)) + 1);
|
|
16
|
-
if (required <= guard) return BigInt(guard);
|
|
17
|
-
guard = required;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
describe('Decimal construction', () => {
|
|
22
|
-
it('creates decimals from native numbers', () => {
|
|
23
|
-
const value = Decimal(123.45);
|
|
24
|
-
expect(isDecimal(value)).toBe(true);
|
|
25
|
-
expect(value.coeff).toBe(12345n);
|
|
26
|
-
expect(value.digits).toBe(2n);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('creates decimals from bigint payloads', () => {
|
|
30
|
-
const value = Decimal({ coeff: 987654321n, digits: 5n });
|
|
31
|
-
expect(value.coeff).toBe(987654321n);
|
|
32
|
-
expect(value.digits).toBe(5n);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('returns existing decimal instances unchanged', () => {
|
|
36
|
-
const value = Decimal(42);
|
|
37
|
-
expect(Decimal(value)).toBe(value);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('returns nullish inputs unchanged', () => {
|
|
41
|
-
expect(Decimal(null)).toBeNull();
|
|
42
|
-
expect(Decimal(undefined)).toBeUndefined();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('Decimal arithmetic', () => {
|
|
47
|
-
it('adds decimals with varying exponents', () => {
|
|
48
|
-
const sum = Decimal(12.345).add(0.655);
|
|
49
|
-
expect(sum.toString()).toBe('13.000');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('preserves exponent when sums cancel to zero', () => {
|
|
53
|
-
const left = Decimal({ coeff: 1234500n, digits: 4n });
|
|
54
|
-
const right = Decimal({ coeff: -1234500n, digits: 4n });
|
|
55
|
-
const sum = left.add(right);
|
|
56
|
-
expect(sum.digits).toBe(4n);
|
|
57
|
-
expect(sum.toString()).toBe('0.0000');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('subtracts decimals correctly', () => {
|
|
61
|
-
const diff = Decimal(10).sub(2.75);
|
|
62
|
-
expect(diff.toString()).toBe(Decimal(7.25).toString());
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('multiplies decimals and combines exponents', () => {
|
|
66
|
-
const product = Decimal(1.5).mul(4);
|
|
67
|
-
expect(product.toString()).toBe('6.0');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('divides with finite decimal expansion', () => {
|
|
71
|
-
const quotient = Decimal(7).div(2);
|
|
72
|
-
expect(quotient.rescale().toString()).toBe('3.5');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('divides with explicit digits', () => {
|
|
76
|
-
const quotient = Decimal(1).div(3, 5n);
|
|
77
|
-
expect(quotient.toString()).toBe('0.33333');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('rounds divisions with negative divisors correctly', () => {
|
|
81
|
-
const quotient = Decimal(1).clone();
|
|
82
|
-
quotient.div$(-3, 0n, 'round');
|
|
83
|
-
expect(quotient.toString()).toBe('0');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('treats negative digit counts as zero when dividing', () => {
|
|
87
|
-
const quotient = Decimal(1).div(Decimal(3), -2n);
|
|
88
|
-
expect(quotient.toString()).toBe('0');
|
|
89
|
-
expect(quotient.digits).toBe(0n);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('scales operands to align fractional digits when dividing', () => {
|
|
93
|
-
const quotient = Decimal('1.23').div(3, 2n);
|
|
94
|
-
expect(quotient.toString()).toBe('0.41');
|
|
95
|
-
expect(quotient.digits).toBe(2n);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('throws when dividing by zero', () => {
|
|
99
|
-
expect(() => Decimal(5).div(0)).toThrow('Division by zero');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('preserves exponent when division result is zero', () => {
|
|
103
|
-
const dividend = Decimal({ coeff: 0n, digits: 4n });
|
|
104
|
-
const result = dividend.div(5);
|
|
105
|
-
expect(result.digits).toBe(4n);
|
|
106
|
-
expect(result.toString()).toBe('0.0000');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('rejects non-integer digit counts for division', () => {
|
|
110
|
-
expect(() => Decimal(1).div(2, 1.2)).toThrow('Digits must be an integer');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('ignores unknown rounding modes during division', () => {
|
|
114
|
-
const value = Decimal(10).clone();
|
|
115
|
-
value.div$(3, 0n, 'unknown' as RoundingMode);
|
|
116
|
-
expect(value.toString()).toBe(Decimal(3).toString());
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe('Decimal transforms', () => {
|
|
121
|
-
it('clones decimals without sharing state', () => {
|
|
122
|
-
const original = Decimal('123.45');
|
|
123
|
-
const copy = original.clone();
|
|
124
|
-
expect(copy).not.toBe(original);
|
|
125
|
-
expect(copy.eq(original)).toBe(true);
|
|
126
|
-
copy.add$(1);
|
|
127
|
-
expect(original.toString()).toBe(Decimal('123.45').toString());
|
|
128
|
-
expect(copy.toString()).toBe(Decimal('124.45').toString());
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('computes absolute values', () => {
|
|
132
|
-
const result = Decimal(-3.25).abs();
|
|
133
|
-
expect(result.toString()).toBe(Decimal(3.25).toString());
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('floors to the requested digit precision', () => {
|
|
137
|
-
expect(Decimal(3.75).floor(1).toString()).toBe(Decimal('3.7').toString());
|
|
138
|
-
expect(Decimal(-3.75).floor(1).toString()).toBe(Decimal('-3.8').toString());
|
|
139
|
-
expect(Decimal(123.45).floor().toString()).toBe(Decimal(123).toString());
|
|
140
|
-
expect(Decimal(123.45).floor(-1).toString()).toBe(Decimal(120).toString());
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('skips floor adjustment when precision is already coarse enough', () => {
|
|
144
|
-
const value = Decimal('1.23');
|
|
145
|
-
const floored = value.floor$(5n);
|
|
146
|
-
expect(floored).toBe(value);
|
|
147
|
-
expect(value.toString()).toBe('1.23');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('rounds half away from zero at the requested digits', () => {
|
|
151
|
-
expect(Decimal(3.5).round().toString()).toBe(Decimal(4).toString());
|
|
152
|
-
expect(Decimal(-3.5).round().toString()).toBe(Decimal(-4).toString());
|
|
153
|
-
expect(Decimal(3.245).round(2).toString()).toBe(Decimal('3.25').toString());
|
|
154
|
-
expect(Decimal(-3.245).round(2).toString()).toBe(Decimal('-3.25').toString());
|
|
155
|
-
expect(Decimal(125).round(-1).toString()).toBe(Decimal(130).toString());
|
|
156
|
-
expect(Decimal(-125).round(-1).toString()).toBe(Decimal(-130).toString());
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('ceils using digit precision', () => {
|
|
160
|
-
expect(Decimal(3.21).ceil(1).toString()).toBe(Decimal('3.3').toString());
|
|
161
|
-
expect(Decimal(-3.21).ceil(1).toString()).toBe(Decimal('-3.2').toString());
|
|
162
|
-
expect(Decimal(45).ceil(-1).toString()).toBe(Decimal(50).toString());
|
|
163
|
-
expect(Decimal(-45).ceil(-1).toString()).toBe(Decimal(-40).toString());
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('skips ceil adjustment when precision is already coarse enough', () => {
|
|
167
|
-
const value = Decimal('-1.2');
|
|
168
|
-
const ceiled = value.ceil$(5n);
|
|
169
|
-
expect(ceiled).toBe(value);
|
|
170
|
-
expect(value.toString()).toBe('-1.2');
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('truncates digits toward zero', () => {
|
|
174
|
-
expect(Decimal(3.987).trunc(2).toString()).toBe(Decimal('3.98').toString());
|
|
175
|
-
expect(Decimal(-3.987).trunc(2).toString()).toBe(Decimal('-3.98').toString());
|
|
176
|
-
expect(Decimal(678).trunc(-2).toString()).toBe(Decimal(600).toString());
|
|
177
|
-
expect(Decimal(-678).trunc(-2).toString()).toBe(Decimal(-600).toString());
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('keeps the target exponent when truncating', () => {
|
|
181
|
-
const value = Decimal({ coeff: 375n, digits: 2n });
|
|
182
|
-
const truncated = value.trunc(2);
|
|
183
|
-
expect(truncated.digits).toBe(2n);
|
|
184
|
-
expect(truncated.toString()).toBe('3.75');
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('splits into floor and fractional parts by default', () => {
|
|
188
|
-
const value = Decimal(4.25);
|
|
189
|
-
const [integral, fractional] = value.split();
|
|
190
|
-
expect(integral.toString()).toBe(Decimal(4).toString());
|
|
191
|
-
expect(fractional.toString()).toBe(Decimal(0.25).toString());
|
|
192
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('splits using trunc mode', () => {
|
|
196
|
-
const value = Decimal(-1.875);
|
|
197
|
-
const [integral, fractional] = value.split(undefined, 'trunc');
|
|
198
|
-
expect(integral.toString()).toBe(Decimal(-1).toString());
|
|
199
|
-
expect(fractional.toString()).toBe(Decimal('-0.875').toString());
|
|
200
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('splits using ceil mode', () => {
|
|
204
|
-
const value = Decimal(-2.125);
|
|
205
|
-
const [integral, fractional] = value.split(undefined, 'ceil');
|
|
206
|
-
expect(integral.toString()).toBe(Decimal(-2).toString());
|
|
207
|
-
expect(fractional.toString()).toBe(Decimal('-0.125').toString());
|
|
208
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('splits using round mode', () => {
|
|
212
|
-
const value = Decimal(2.5);
|
|
213
|
-
const [integral, fractional] = value.split(undefined, 'round');
|
|
214
|
-
expect(integral.toString()).toBe(Decimal(3).toString());
|
|
215
|
-
expect(fractional.toString()).toBe(Decimal('-0.5').toString());
|
|
216
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('splits with digit precision', () => {
|
|
220
|
-
const value = Decimal('12.3456');
|
|
221
|
-
const [integral, fractional] = value.split(2);
|
|
222
|
-
expect(integral.toString()).toBe(Decimal('12.34').toString());
|
|
223
|
-
expect(fractional.toString()).toBe(Decimal('0.0056').toString());
|
|
224
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('splits with digit precision in trunc mode', () => {
|
|
228
|
-
const value = Decimal('-7.987');
|
|
229
|
-
const [integral, fractional] = value.split(1, 'trunc');
|
|
230
|
-
expect(integral.toString()).toBe(Decimal('-7.9').toString());
|
|
231
|
-
expect(fractional.toString()).toBe(Decimal('-0.087').toString());
|
|
232
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it('splits by step using floor mode by default', () => {
|
|
236
|
-
const value = Decimal('5.83');
|
|
237
|
-
const [integral, fractional] = value.splitBy('0.25');
|
|
238
|
-
expect(integral.toString()).toBe(Decimal('5.75').toString());
|
|
239
|
-
expect(fractional.toString()).toBe(Decimal('0.08').toString());
|
|
240
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('splits by step using trunc mode', () => {
|
|
244
|
-
const value = Decimal('-3.6');
|
|
245
|
-
const [integral, fractional] = value.splitBy('0.5', 'trunc');
|
|
246
|
-
expect(integral.toString()).toBe(Decimal('-3.5').toString());
|
|
247
|
-
expect(fractional.toString()).toBe(Decimal('-0.1').toString());
|
|
248
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it('splits by step using ceil mode', () => {
|
|
252
|
-
const value = Decimal(-4.1);
|
|
253
|
-
const [integral, fractional] = value.splitBy(0.5, 'ceil');
|
|
254
|
-
expect(integral.toString()).toBe('-4.0');
|
|
255
|
-
expect(fractional.toString()).toBe(Decimal('-0.1').toString());
|
|
256
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it('splits by step using round mode', () => {
|
|
260
|
-
const value = Decimal('5.62');
|
|
261
|
-
const [integral, fractional] = value.splitBy('0.25', 'round');
|
|
262
|
-
expect(integral.toString()).toBe('5.50');
|
|
263
|
-
expect(fractional.toString()).toBe(Decimal('0.12').toString());
|
|
264
|
-
expect(integral.add(fractional).eq(value)).toBe(true);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe('Decimal type guards', () => {
|
|
269
|
-
it('detects decimals using isDecimal', () => {
|
|
270
|
-
const value = Decimal(7);
|
|
271
|
-
expect(Decimal.isDecimal(value)).toBe(true);
|
|
272
|
-
expect(Decimal.isDecimal(7)).toBe(false);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('recognizes supported literal inputs', () => {
|
|
276
|
-
expect(Decimal.isDecimalLike('42.00')).toBe(true);
|
|
277
|
-
expect(Decimal.isDecimalLike(42)).toBe(true);
|
|
278
|
-
expect(Decimal.isDecimalLike(42n)).toBe(true);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('validates structured decimal-like payloads', () => {
|
|
282
|
-
expect(Decimal.isDecimalLike(Decimal(7))).toBe(true);
|
|
283
|
-
expect(Decimal.isDecimalLike({ coeff: 123n, digits: 4n })).toBe(true);
|
|
284
|
-
expect(Decimal.isDecimalLike({ coeff: 123, digits: 4n })).toBe(false);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
describe('Decimal number parity', () => {
|
|
289
|
-
it('matches Number(str) for challenging inputs', () => {
|
|
290
|
-
const samples = [
|
|
291
|
-
'0',
|
|
292
|
-
'5e-324',
|
|
293
|
-
'1e-324',
|
|
294
|
-
'1e-4000',
|
|
295
|
-
'1e4000',
|
|
296
|
-
'1.7976931348623157e308',
|
|
297
|
-
'2.2250738585072014e-308',
|
|
298
|
-
'9007199254740991',
|
|
299
|
-
'9007199254740993',
|
|
300
|
-
' 3.1415926535897932384626433832795028841971 ',
|
|
301
|
-
];
|
|
302
|
-
|
|
303
|
-
for (const input of samples) {
|
|
304
|
-
const decimalValue = Decimal(input).number();
|
|
305
|
-
const nativeValue = Number(input);
|
|
306
|
-
if (Number.isNaN(nativeValue)) {
|
|
307
|
-
expect(decimalValue).toBeNaN();
|
|
308
|
-
} else {
|
|
309
|
-
expect(decimalValue).toBe(nativeValue);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
describe('pow10', () => {
|
|
316
|
-
it('returns one when exponent is zero', () => {
|
|
317
|
-
const value = pow10(0n);
|
|
318
|
-
expect(value.toString()).toBe(Decimal(1).toString());
|
|
319
|
-
expect(value.digits).toBe(0n);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it('represents positive exponents using negative digit counts', () => {
|
|
323
|
-
const value = pow10(5n);
|
|
324
|
-
expect(value.coeff).toBe(1n);
|
|
325
|
-
expect(value.digits).toBe(-5n);
|
|
326
|
-
expect(value.number()).toBe(100000);
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
it('handles negative exponents with fractional results', () => {
|
|
330
|
-
const value = pow10(-3n);
|
|
331
|
-
expect(value.coeff).toBe(1n);
|
|
332
|
-
expect(value.digits).toBe(3n);
|
|
333
|
-
expect(value.toString()).toBe(Decimal('0.001').toString());
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe('Decimal shift10', () => {
|
|
338
|
-
it('shifts by positive exponents without changing the coefficient', () => {
|
|
339
|
-
const original = Decimal('12.345');
|
|
340
|
-
const shifted = original.shift10(2n);
|
|
341
|
-
expect(shifted.toString()).toBe(Decimal('1234.5').toString());
|
|
342
|
-
expect(shifted.coeff).toBe(original.coeff);
|
|
343
|
-
expect(shifted.digits).toBe(1n);
|
|
344
|
-
expect(original.toString()).toBe(Decimal('12.345').toString());
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('shifts by negative exponents in place with shift10$', () => {
|
|
348
|
-
const value = Decimal('9876.5');
|
|
349
|
-
value.shift10$(-3);
|
|
350
|
-
expect(value.toString()).toBe(Decimal('9.8765').toString());
|
|
351
|
-
expect(value.digits).toBe(4n);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('ignores zero shifts in place', () => {
|
|
355
|
-
const value = Decimal('1.2345');
|
|
356
|
-
const shifted = value.shift10$(0);
|
|
357
|
-
expect(shifted).toBe(value);
|
|
358
|
-
expect(value.toString()).toBe('1.2345');
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('rejects non-integer shift amounts', () => {
|
|
362
|
-
expect(() => Decimal(1).shift10(1.2)).toThrow('Shift amount must be an integer');
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
describe('Decimal sign helpers', () => {
|
|
367
|
-
it('identifies zero, positive, and negative values', () => {
|
|
368
|
-
const zero = Decimal({ coeff: 0n, digits: 3n });
|
|
369
|
-
const positive = Decimal({ coeff: 123n, digits: 2n });
|
|
370
|
-
const negative = Decimal({ coeff: -45n, digits: 1n });
|
|
371
|
-
expect(zero.isZero()).toBe(true);
|
|
372
|
-
expect(zero.isPositive()).toBe(false);
|
|
373
|
-
expect(zero.isNegative()).toBe(false);
|
|
374
|
-
expect(positive.isZero()).toBe(false);
|
|
375
|
-
expect(positive.isPositive()).toBe(true);
|
|
376
|
-
expect(positive.isNegative()).toBe(false);
|
|
377
|
-
expect(negative.isZero()).toBe(false);
|
|
378
|
-
expect(negative.isPositive()).toBe(false);
|
|
379
|
-
expect(negative.isNegative()).toBe(true);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
describe('sign', () => {
|
|
383
|
-
it('returns a 0', () => {
|
|
384
|
-
const value = Decimal(0);
|
|
385
|
-
expect(value.sign().toString()).toBe('0');
|
|
386
|
-
});
|
|
387
|
-
it('returns a -1', () => {
|
|
388
|
-
const value = Decimal(-1.987);
|
|
389
|
-
expect(value.sign().toString()).toBe('-1');
|
|
390
|
-
});
|
|
391
|
-
it('returns a 1', () => {
|
|
392
|
-
const value = Decimal(1.987);
|
|
393
|
-
expect(value.sign().toString()).toBe('1');
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
describe('neg', () => {
|
|
398
|
-
it('returns negated value', () => {
|
|
399
|
-
const value = Decimal(1.987);
|
|
400
|
-
expect(value.neg(true).toString()).toBe('-1.987');
|
|
401
|
-
});
|
|
402
|
-
it('returns original value', () => {
|
|
403
|
-
const value = Decimal(1.987);
|
|
404
|
-
expect(value.neg(false).toString()).toBe('1.987');
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
describe('Decimal truncation', () => {
|
|
410
|
-
it('returns a new instance truncated to the requested digits', () => {
|
|
411
|
-
const original = Decimal(1.2345);
|
|
412
|
-
const truncated = original.trunc(2);
|
|
413
|
-
expect(truncated.toString()).toBe('1.23');
|
|
414
|
-
expect(original.toString()).toBe('1.2345');
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('mutates in place with trunc$', () => {
|
|
418
|
-
const value = Decimal(-1.987);
|
|
419
|
-
value.trunc$(1);
|
|
420
|
-
expect(value.toString()).toBe('-1.9');
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
it('preserves target digits when truncating zero', () => {
|
|
424
|
-
const value = Decimal({ coeff: 0n, digits: 5n });
|
|
425
|
-
const truncated = value.trunc(3);
|
|
426
|
-
expect(truncated.digits).toBe(3n);
|
|
427
|
-
expect(truncated.toString()).toBe('0.000');
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
describe('Decimal proximity checks', () => {
|
|
432
|
-
it('detects differences within absolute tolerance', () => {
|
|
433
|
-
const base = Decimal(1.2345);
|
|
434
|
-
const nearby = Decimal(1.2349);
|
|
435
|
-
expect(base.isCloseTo(nearby, 0.0005)).toBe(true);
|
|
436
|
-
expect(base.isCloseTo(nearby, 0.0003)).toBe(false);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('treats identical values as within zero tolerance', () => {
|
|
440
|
-
const value = Decimal({ coeff: 42000n, digits: 3n });
|
|
441
|
-
expect(value.isCloseTo(value, 0)).toBe(true);
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('rejects negative tolerance values', () => {
|
|
445
|
-
const base = Decimal(1);
|
|
446
|
-
expect(() => base.isCloseTo(Decimal(1.1), -0.1)).toThrow('Tolerance must be non-negative');
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
describe('Decimal pow', () => {
|
|
451
|
-
it('raises to integer exponents', () => {
|
|
452
|
-
const value = Decimal(3);
|
|
453
|
-
const powered = value.pow(4n, 8n);
|
|
454
|
-
expect(powered.toString()).toBe('81.00000000');
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('handles fractional exponents with requested precision', () => {
|
|
458
|
-
const result = Decimal(2).pow(Decimal({ coeff: 15n, digits: 1n }), 9n);
|
|
459
|
-
expect(result.number()).toBeCloseTo(Math.pow(2, 1.5), 8);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('supports negative exponents by returning reciprocals', () => {
|
|
463
|
-
const result = Decimal(8).pow(-2n, 9n);
|
|
464
|
-
expect(result.toString()).toBe('0.015625000');
|
|
465
|
-
expect(result.rescale().toString()).toBe('0.015625');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('rounds reciprocal results to requested digits', () => {
|
|
469
|
-
const result = Decimal(3).pow(-1n, 2n);
|
|
470
|
-
expect(result.toString()).toBe('0.33');
|
|
471
|
-
expect(result.digits).toBe(2n);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it('preserves zero exponent when base is zero', () => {
|
|
475
|
-
const base = Decimal({ coeff: 0n, digits: 4n });
|
|
476
|
-
const result = base.pow(2n, 8n);
|
|
477
|
-
expect(result.digits).toBe(4n);
|
|
478
|
-
expect(result.toString()).toBe('0.0000');
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
it('returns unity for zero exponents', () => {
|
|
482
|
-
const result = Decimal('123.456').pow(0n, 6n);
|
|
483
|
-
expect(result.toString()).toBe(Decimal(1).toString());
|
|
484
|
-
expect(result.digits).toBe(0n);
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it('matches high-precision reference for fractional exponents', () => {
|
|
488
|
-
const base = Decimal('1.0000000001');
|
|
489
|
-
const exponent = Decimal('0.9876543210123456789');
|
|
490
|
-
const digits = 60n;
|
|
491
|
-
const highPrecision = base.pow(exponent, digits + 30n).round(digits);
|
|
492
|
-
const result = base.pow(exponent, digits);
|
|
493
|
-
expect(result.round(digits).eq(highPrecision)).toBe(true);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it('preserves precision for negative fractional exponents', () => {
|
|
497
|
-
const base = Decimal('7.8125');
|
|
498
|
-
const exponent = Decimal('-0.27182818284590452353');
|
|
499
|
-
const digits = 50n;
|
|
500
|
-
const highPrecision = base.pow(exponent, digits + 30n).round(digits);
|
|
501
|
-
const result = base.pow(exponent, digits);
|
|
502
|
-
expect(result.round(digits).eq(highPrecision)).toBe(true);
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
describe('Decimal logarithms', () => {
|
|
507
|
-
it('computes logarithms with precision for bases greater than one', () => {
|
|
508
|
-
const value = Decimal(125);
|
|
509
|
-
const result = value.log(5, 10n);
|
|
510
|
-
expect(result.toString()).toBe(Decimal(3).toString());
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
it('computes logarithms for fractional values', () => {
|
|
514
|
-
const value = Decimal(0.05);
|
|
515
|
-
const result = value.log(Decimal(10), 6n);
|
|
516
|
-
const expected = Decimal({ coeff: -1301030n, digits: 6n });
|
|
517
|
-
expect(result.isCloseTo(expected, 1e-6)).toBe(true);
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it('supports bases between zero and one', () => {
|
|
521
|
-
const value = Decimal(2);
|
|
522
|
-
const result = value.log(Decimal({ coeff: 5n, digits: 1n }), 8n);
|
|
523
|
-
expect(result.isCloseTo(Decimal(-1), 1e-7)).toBe(true);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
it('round-trips with pow using the same precision', () => {
|
|
527
|
-
const base = Decimal(7.5);
|
|
528
|
-
const value = Decimal(123.456);
|
|
529
|
-
const logValue = value.log(base, 8n);
|
|
530
|
-
const reconstructed = base.pow(logValue, 8n);
|
|
531
|
-
|
|
532
|
-
expect(reconstructed.toFixed(8)).toBe(value.toFixed(8));
|
|
533
|
-
// expect(reconstructed.isCloseTo(value, 1e-8)).toBe(true);
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
it('computes negative integer logarithms for inverse powers', () => {
|
|
537
|
-
const value = Decimal('0.001');
|
|
538
|
-
const result = value.log(Decimal(10), 6n);
|
|
539
|
-
expect(result.toString()).toBe(Decimal(-3).toString());
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('returns zero when logging unity', () => {
|
|
543
|
-
const result = Decimal(1).log(Decimal(10), 8n);
|
|
544
|
-
expect(result.toString()).toBe(Decimal(0).toString());
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it('returns correct integer part for near-unity ratios', () => {
|
|
548
|
-
const base = Decimal('1.0000000001');
|
|
549
|
-
const value = Decimal('1.000000000099999999');
|
|
550
|
-
const result = value.log(base, 0n);
|
|
551
|
-
expect(result.toString()).toBe(Decimal(1).toString());
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
it('retains integer part for powers with tiny deltas', () => {
|
|
555
|
-
const base = Decimal('1.00000000001');
|
|
556
|
-
const powered = base.pow(2n, 32n);
|
|
557
|
-
const result = powered.log(base, 0n);
|
|
558
|
-
expect(result.toString()).toBe(Decimal(2).toString());
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it('matches coarse precision with high-precision reference near unity', () => {
|
|
562
|
-
const base = Decimal('1.000000000001');
|
|
563
|
-
const value = Decimal('1.000000000009');
|
|
564
|
-
const highPrecision = value.log(base, 20n).round(0n);
|
|
565
|
-
const coarse = value.log(base, 0n);
|
|
566
|
-
expect(coarse.round(0n).eq(highPrecision)).toBe(true);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
it('treats negative digit counts as zero when logging', () => {
|
|
570
|
-
const withNegativeDigits = Decimal(10).log(Decimal(10), -1n);
|
|
571
|
-
const baseline = Decimal(10).log(Decimal(10), 0n);
|
|
572
|
-
expect(withNegativeDigits.eq(baseline)).toBe(true);
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
it('returns logarithms with requested fractional digits', () => {
|
|
576
|
-
const digits = 6n;
|
|
577
|
-
const result = Decimal(0.05).log(Decimal(10), digits);
|
|
578
|
-
expect(result.digits >= digits).toBe(true);
|
|
579
|
-
expect(result.round(digits).digits).toBe(digits);
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
it('rounds logarithm output to the requested fractional digits', () => {
|
|
583
|
-
const digits = 6n;
|
|
584
|
-
const result = Decimal(0.05).log(Decimal(10), digits);
|
|
585
|
-
expect(result.digits <= digits + 6n).toBe(true);
|
|
586
|
-
expect(result.round(digits).toString()).toBe('-1.301030');
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
it('keeps guard digits minimal when zero precision is requested', () => {
|
|
590
|
-
const digits = 0n;
|
|
591
|
-
const base = Decimal(10);
|
|
592
|
-
const value = Decimal(0.05);
|
|
593
|
-
const result = value.log(base, digits);
|
|
594
|
-
const allowance = guardAllowance(digits, base, value);
|
|
595
|
-
expect(result.digits <= digits + allowance).toBe(true);
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
it('scales guard digits logarithmically with requested precision', () => {
|
|
599
|
-
const digits = 80n;
|
|
600
|
-
const base = Decimal(10);
|
|
601
|
-
const value = Decimal(0.05);
|
|
602
|
-
const result = value.log(base, digits);
|
|
603
|
-
const allowance = guardAllowance(digits, base, value);
|
|
604
|
-
expect(result.digits <= digits + allowance).toBe(true);
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
it('rejects non-positive arguments', () => {
|
|
608
|
-
expect(() => Decimal(0).log(Decimal(10), 6n)).toThrow('Logarithm argument must be positive');
|
|
609
|
-
expect(() => Decimal(-5).log(Decimal(10), 6n)).toThrow('Logarithm argument must be positive');
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
it('rejects non-positive bases', () => {
|
|
613
|
-
expect(() => Decimal(10).log(Decimal(0), 6n)).toThrow('Logarithm base must be positive');
|
|
614
|
-
expect(() => Decimal(10).log(Decimal(-2), 6n)).toThrow('Logarithm base must be positive');
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
it('rejects base equal to one', () => {
|
|
618
|
-
expect(() => Decimal(10).log(Decimal(1), 6n)).toThrow('Logarithm base cannot be one');
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
it('computes logarithms with fractional digit control for other bases', () => {
|
|
622
|
-
const result = Decimal(3).log(Decimal(2), 6n);
|
|
623
|
-
const expected = Decimal({ coeff: 1584963n, digits: 6n });
|
|
624
|
-
expect(result.isCloseTo(expected, 1e-6)).toBe(true);
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
describe('Decimal order', () => {
|
|
629
|
-
it('returns integer exponents for powers of ten', () => {
|
|
630
|
-
const result = Decimal(1000).order();
|
|
631
|
-
expect(result).toBe(3n);
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
it('returns the integer part of the base-10 logarithm', () => {
|
|
635
|
-
const result = Decimal(987654.321).order();
|
|
636
|
-
expect(result).toBe(5n);
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
it('returns negative exponents for sub-unit values', () => {
|
|
640
|
-
const result = Decimal(0.05).order();
|
|
641
|
-
expect(result).toBe(-2n);
|
|
642
|
-
});
|
|
643
|
-
|
|
644
|
-
it('rejects 0 arguments', () => {
|
|
645
|
-
expect(() => Decimal(0n).order()).toThrow();
|
|
646
|
-
});
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
describe('Decimal roots', () => {
|
|
650
|
-
it('computes square roots with requested precision', () => {
|
|
651
|
-
const root = Decimal(2).sqrt(10n);
|
|
652
|
-
expect(root.number()).toBeCloseTo(Math.sqrt(2), 9);
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it('mutates in place when using sqrt$', () => {
|
|
656
|
-
const value = Decimal('7.29');
|
|
657
|
-
value.sqrt$(4n);
|
|
658
|
-
expect(value.toString()).toBe('2.7000');
|
|
659
|
-
expect(value.rescale().toString()).toBe('2.7');
|
|
660
|
-
expect(value.digits).toBe(4n);
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it('matches general root computation for sqrt$', () => {
|
|
664
|
-
const input = Decimal('0.000625');
|
|
665
|
-
const clone = input.clone();
|
|
666
|
-
const sqrtDigits = 8n;
|
|
667
|
-
input.sqrt$(sqrtDigits);
|
|
668
|
-
const viaRoot = clone.root$(2n, sqrtDigits);
|
|
669
|
-
expect(input.toFixed(sqrtDigits)).toBe(viaRoot.toFixed(sqrtDigits));
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
it('computes general integer roots', () => {
|
|
673
|
-
const root = Decimal(81).root(4n, 8n);
|
|
674
|
-
expect(root.toString()).toBe('3.00000000');
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it('computes roots safely when fallback inverse is unity', () => {
|
|
678
|
-
const root = Decimal(9).root(2n, 8n);
|
|
679
|
-
expect(root.toString()).toBe('3.00000000');
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
it('supports odd roots of negative numbers', () => {
|
|
683
|
-
const root = Decimal(-27).root(3n, 8n);
|
|
684
|
-
expect(root.toString()).toBe('-3.00000000');
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
it('uses floating approximations for moderate roots', () => {
|
|
688
|
-
const powSpy = vi.spyOn(Math, 'pow');
|
|
689
|
-
const root = Decimal(256).root(4n, 6n);
|
|
690
|
-
expect(powSpy).toHaveBeenCalled();
|
|
691
|
-
expect(root.eq(Decimal(4))).toBe(true);
|
|
692
|
-
powSpy.mockRestore();
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
it('throws on even roots of negative numbers', () => {
|
|
696
|
-
expect(() => Decimal(-16).root(2n, 8n)).toThrowError();
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
it('preserves exponent when root of zero is taken', () => {
|
|
700
|
-
const value = Decimal({ coeff: 0n, digits: 6n });
|
|
701
|
-
const root = value.root(3n, 6n);
|
|
702
|
-
expect(root.digits).toBe(6n);
|
|
703
|
-
expect(root.toString()).toBe('0.000000');
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it('rejects non-positive root degrees', () => {
|
|
707
|
-
expect(() => Decimal(9).root(0n, 4n)).toThrow('Invalid root degree');
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
it('treats positive precision exponents as zero when taking roots', () => {
|
|
711
|
-
const root = Decimal(9).root(2n, -1n);
|
|
712
|
-
expect(root.toString()).toBe(Decimal(3).toString());
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
it('computes large-magnitude roots without relying on floating guesses', () => {
|
|
716
|
-
const value = Decimal({ coeff: 1n, digits: -2000n });
|
|
717
|
-
const root = value.root(2n, 12n);
|
|
718
|
-
const expected = Decimal({ coeff: 1n, digits: -1000n });
|
|
719
|
-
expect(root.eq(expected)).toBe(true);
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
it('computes tiny-magnitude roots without relying on floating guesses', () => {
|
|
723
|
-
const value = Decimal({ coeff: 1n, digits: 2000n });
|
|
724
|
-
const root = value.root(2n, 1200n);
|
|
725
|
-
const expected = Decimal({ coeff: 1n, digits: 1000n });
|
|
726
|
-
expect(root.eq(expected)).toBe(true);
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
it('reseeds zero root estimates when coarse precision truncates guesses', () => {
|
|
730
|
-
const value = Decimal({ coeff: 1n, digits: 2000n });
|
|
731
|
-
const root = value.root(10n, 0n);
|
|
732
|
-
expect(root.toString()).toBe('0');
|
|
733
|
-
expect(root.digits).toBe(0n);
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
it('handles high-degree roots for tiny magnitudes with coarse precision', () => {
|
|
737
|
-
const value = Decimal({ coeff: 1n, digits: 2000n });
|
|
738
|
-
const root = value.root(20n, 0n);
|
|
739
|
-
expect(root.toString()).toBe('0');
|
|
740
|
-
expect(root.digits).toBe(0n);
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
it('matches high-precision power check for fractional roots', () => {
|
|
744
|
-
const value = Decimal('98765.4321');
|
|
745
|
-
const degree = 5n;
|
|
746
|
-
const digits = 60n;
|
|
747
|
-
const root = value.root(degree, digits);
|
|
748
|
-
const recomposed = root.pow(degree, digits + 30n).rescale(digits);
|
|
749
|
-
const tolerance = pow10(-(digits - 4n));
|
|
750
|
-
expect(recomposed.isCloseTo(value.rescale(digits), tolerance)).toBe(true);
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
it('retains precision for odd roots of negatives', () => {
|
|
754
|
-
const value = Decimal('-42.424242');
|
|
755
|
-
const degree = 3n;
|
|
756
|
-
const digits = 50n;
|
|
757
|
-
const root = value.root(degree, digits);
|
|
758
|
-
const recomposed = root.pow(degree, digits + 30n).rescale(digits);
|
|
759
|
-
const tolerance = pow10(-(digits - 4n));
|
|
760
|
-
expect(recomposed.isCloseTo(value.rescale(digits), tolerance)).toBe(true);
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
it('falls back to order-based estimates when floating guesses overflow', () => {
|
|
764
|
-
const powSpy = vi.spyOn(Math, 'pow');
|
|
765
|
-
const huge = Decimal({ coeff: 1n, digits: -5000n });
|
|
766
|
-
const root = huge.root(2n, 4n);
|
|
767
|
-
expect(powSpy).not.toHaveBeenCalled();
|
|
768
|
-
expect(root.digits).toBe(4n);
|
|
769
|
-
powSpy.mockRestore();
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
it('recovers when floating approximation yields a non-finite guess', () => {
|
|
773
|
-
const powSpy = vi.spyOn(Math, 'pow').mockReturnValueOnce(Number.NaN);
|
|
774
|
-
const root = Decimal(64).root(3n, 8n);
|
|
775
|
-
expect(powSpy).toHaveBeenCalled();
|
|
776
|
-
expect(root.eq(Decimal(4))).toBe(true);
|
|
777
|
-
powSpy.mockRestore();
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
it('treats non-positive numeric degree hints as fallbacks while keeping bigint logic', () => {
|
|
781
|
-
const originalNumber = Number;
|
|
782
|
-
const targetDegree = 2n;
|
|
783
|
-
const mockNumber = function (value: unknown) {
|
|
784
|
-
if (value === targetDegree) return 0;
|
|
785
|
-
return originalNumber(value as never);
|
|
786
|
-
} as NumberConstructor;
|
|
787
|
-
for (const key of Object.getOwnPropertyNames(originalNumber)) {
|
|
788
|
-
const descriptor = Object.getOwnPropertyDescriptor(originalNumber, key);
|
|
789
|
-
if (descriptor) {
|
|
790
|
-
Object.defineProperty(mockNumber, key, descriptor);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
(globalThis as { Number: NumberConstructor }).Number = mockNumber;
|
|
795
|
-
try {
|
|
796
|
-
const root = Decimal(16).root(targetDegree, 6n);
|
|
797
|
-
expect(root.eq(Decimal(4))).toBe(true);
|
|
798
|
-
} finally {
|
|
799
|
-
(globalThis as { Number: NumberConstructor }).Number = originalNumber;
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
describe('Decimal modular helpers', () => {
|
|
805
|
-
it('computes modulo respecting signs', () => {
|
|
806
|
-
expect(Decimal(17).mod(5).toString()).toBe(Decimal(2).toString());
|
|
807
|
-
expect(Decimal(-17).mod(5).toString()).toBe(Decimal(-2).toString());
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
it('returns positive modulo', () => {
|
|
811
|
-
expect(Decimal(-17).modPositive(5).toString()).toBe(Decimal(3).toString());
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
it('leaves positive remainders unchanged in modPositive', () => {
|
|
815
|
-
const result = Decimal(17).modPositive(5);
|
|
816
|
-
expect(result.toString()).toBe(Decimal(2).toString());
|
|
817
|
-
});
|
|
818
|
-
|
|
819
|
-
it('rejects negative divisors for positive modulo', () => {
|
|
820
|
-
expect(() => Decimal(1).modPositive(-5)).toThrow('Modulo divisor must be positive');
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
it('aligns to multiples with step helpers', () => {
|
|
824
|
-
expect(Decimal(17).floorBy(4).toString()).toBe(Decimal(16).toString());
|
|
825
|
-
expect(Decimal(-17).floorBy(4).toString()).toBe(Decimal(-20).toString());
|
|
826
|
-
expect(Decimal(17).ceilBy(4).toString()).toBe(Decimal(20).toString());
|
|
827
|
-
expect(Decimal(-17).ceilBy(4).toString()).toBe(Decimal(-16).toString());
|
|
828
|
-
expect(Decimal(17).truncBy(4).toString()).toBe(Decimal(16).toString());
|
|
829
|
-
expect(Decimal(-17).truncBy(4).toString()).toBe(Decimal(-16).toString());
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
it('ceilBy advances values that exceed the step by tiny margins', () => {
|
|
833
|
-
const value = Decimal('4.0000000000000000005');
|
|
834
|
-
const aligned = value.ceilBy(1);
|
|
835
|
-
expect(aligned.toString()).toBe('5');
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
it('floorBy drops negatives that are just below the previous multiple', () => {
|
|
839
|
-
const value = Decimal('-4.0000000000000000005');
|
|
840
|
-
const aligned = value.floorBy(1);
|
|
841
|
-
expect(aligned.toString()).toBe('-5');
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
it('rounds to step size using explicit mode', () => {
|
|
845
|
-
expect(Decimal(6).roundBy(4, 'floor').toString()).toBe(Decimal(4).toString());
|
|
846
|
-
expect(Decimal(6).roundBy(4, 'ceil').toString()).toBe(Decimal(8).toString());
|
|
847
|
-
expect(Decimal(-6).roundBy(4, 'ceil').toString()).toBe(Decimal(-4).toString());
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
it('rounds to the nearest step by default', () => {
|
|
851
|
-
expect(Decimal(6).roundBy(4).toString()).toBe(Decimal(8).toString());
|
|
852
|
-
expect(Decimal(2).roundBy(4).toString()).toBe(Decimal(4).toString());
|
|
853
|
-
expect(Decimal(-6).roundBy(4).toString()).toBe(Decimal(-8).toString());
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
it('rounds to the nearest step in place by default', () => {
|
|
857
|
-
const value = Decimal('5.75');
|
|
858
|
-
value.roundBy$('0.5');
|
|
859
|
-
expect(value.toString()).toBe('6.0');
|
|
860
|
-
value.add$(0.24);
|
|
861
|
-
value.roundBy$('0.5');
|
|
862
|
-
expect(value.toString()).toBe('6.0');
|
|
863
|
-
});
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
describe('Decimal comparisons', () => {
|
|
867
|
-
it('compares equality and inequality', () => {
|
|
868
|
-
const a = Decimal(1.234);
|
|
869
|
-
const b = Decimal({ coeff: 1234n, digits: 3n });
|
|
870
|
-
expect(a.eq(b)).toBe(true);
|
|
871
|
-
expect(a.neq(Decimal(2))).toBe(true);
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
it('orders decimals correctly', () => {
|
|
875
|
-
const a = Decimal(-2.5);
|
|
876
|
-
const b = Decimal(-2.4);
|
|
877
|
-
expect(a.lt(b)).toBe(true);
|
|
878
|
-
expect(b.gt(a)).toBe(true);
|
|
879
|
-
expect(a.le(a)).toBe(true);
|
|
880
|
-
expect(b.ge(a)).toBe(true);
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
it('checks inclusive bounds using between', () => {
|
|
884
|
-
expect(Decimal(5).between(1, 10)).toBe(true);
|
|
885
|
-
expect(Decimal(10).between(1, 10)).toBe(true);
|
|
886
|
-
expect(Decimal(0).between(1, 10)).toBe(false);
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
it('handles undefined bounds in between', () => {
|
|
890
|
-
expect(Decimal(5).between(undefined, 5)).toBe(true);
|
|
891
|
-
expect(Decimal(-2).between(-2, undefined)).toBe(true);
|
|
892
|
-
expect(Decimal(12).between(undefined, 5)).toBe(false);
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
it('rejects inverted ranges in between checks', () => {
|
|
896
|
-
expect(() => Decimal(5).between(10, 1)).toThrow('Invalid between range');
|
|
897
|
-
});
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
describe('Decimal presentation', () => {
|
|
901
|
-
it('renders to string with trailing zeros', () => {
|
|
902
|
-
const value = Decimal({ coeff: 12300n, digits: 2n });
|
|
903
|
-
expect(value.toString()).toBe('123.00');
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
it('renders zero respecting exponent', () => {
|
|
907
|
-
const value = Decimal({ coeff: 0n, digits: 3n });
|
|
908
|
-
expect(value.toString()).toBe('0.000');
|
|
909
|
-
});
|
|
910
|
-
|
|
911
|
-
it('renders zero compactly when exponential shorthand is used', () => {
|
|
912
|
-
const value = Decimal('0e5');
|
|
913
|
-
expect(value.toString()).toBe('0');
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
it('renders values less than one with leading zero', () => {
|
|
917
|
-
const value = Decimal(0.005);
|
|
918
|
-
expect(value.toString()).toBe('0.005');
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
it('throws when decimal precision exceeds safe conversion range', () => {
|
|
922
|
-
const huge = Decimal({ coeff: 1n, digits: 9007199254740991n + 1n });
|
|
923
|
-
expect(() => huge.toString()).toThrow(RangeError);
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
it('produces fixed decimals with padding without rounding', () => {
|
|
927
|
-
const value = Decimal(1.2);
|
|
928
|
-
expect(value.toFixed(3)).toBe('1.200');
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
it('rounds half away from zero when fixing digits', () => {
|
|
932
|
-
expect(Decimal(1.235).toFixed(2)).toBe('1.24');
|
|
933
|
-
expect(Decimal(-1.235).toFixed(2)).toBe('-1.24');
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
it('supports bigint fraction digits without mutating original', () => {
|
|
937
|
-
const value = Decimal(2.5);
|
|
938
|
-
const result = value.toFixed(0n);
|
|
939
|
-
expect(result).toBe('3');
|
|
940
|
-
expect(value.toString()).toBe('2.5');
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
it('rejects negative fraction digits', () => {
|
|
944
|
-
expect(() => Decimal(1).toFixed(-1)).toThrow('Fraction digits must be a non-negative integer');
|
|
945
|
-
});
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
describe('NumberLike utilities', () => {
|
|
949
|
-
it('computes min and max across inputs', () => {
|
|
950
|
-
const minResult = min(Decimal(5), 3, 4);
|
|
951
|
-
const maxResult = max(Decimal(5), 7, 4);
|
|
952
|
-
expect(minResult).not.toBeNull();
|
|
953
|
-
expect(maxResult).not.toBeNull();
|
|
954
|
-
expect(minResult?.toString()).toBe(Decimal(3).toString());
|
|
955
|
-
expect(maxResult?.toString()).toBe(Decimal(7).toString());
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
it('computes minmax across inputs', () => {
|
|
959
|
-
const [minValue, maxValue] = minmax(Decimal(5), 3, 4, null);
|
|
960
|
-
expect(minValue).not.toBeNull();
|
|
961
|
-
expect(maxValue).not.toBeNull();
|
|
962
|
-
expect(minValue?.toString()).toBe(Decimal(3).toString());
|
|
963
|
-
expect(maxValue?.toString()).toBe(Decimal(5).toString());
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
it('ignores nullish values when aggregating', () => {
|
|
967
|
-
const minResult = min(null, undefined, 4, Decimal(2));
|
|
968
|
-
const maxResult = max(undefined, null, Decimal(2), 8);
|
|
969
|
-
expect(minResult).not.toBeNull();
|
|
970
|
-
expect(maxResult).not.toBeNull();
|
|
971
|
-
expect(minResult?.toString()).toBe(Decimal(2).toString());
|
|
972
|
-
expect(maxResult?.toString()).toBe(Decimal(8).toString());
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
it('returns null when all inputs are nullish', () => {
|
|
976
|
-
expect(min(null, undefined)).toBeNull();
|
|
977
|
-
expect(max(undefined, null)).toBeNull();
|
|
978
|
-
const [minValue, maxValue] = minmax(undefined, null);
|
|
979
|
-
expect(minValue).toBeNull();
|
|
980
|
-
expect(maxValue).toBeNull();
|
|
981
|
-
const [emptyMin, emptyMax] = minmax();
|
|
982
|
-
expect(emptyMin).toBeNull();
|
|
983
|
-
expect(emptyMax).toBeNull();
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
it('clamps values within bounds', () => {
|
|
987
|
-
expect(Decimal(10).clamp(0, 5).toString()).toBe(Decimal(5).toString());
|
|
988
|
-
expect(Decimal(-1).clamp(0, 5).toString()).toBe(Decimal(0).toString());
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
it('ignores undefined lower bound when clamping', () => {
|
|
992
|
-
const result = Decimal(3).clamp(undefined, Decimal(2));
|
|
993
|
-
expect(result.toString()).toBe(Decimal(2).toString());
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
it('ignores undefined upper bound when clamping', () => {
|
|
997
|
-
const result = Decimal(-3).clamp(Decimal(-1), undefined);
|
|
998
|
-
expect(result.toString()).toBe(Decimal(-1).toString());
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
it('creates decimals from string inputs', () => {
|
|
1002
|
-
expect(Decimal('123.45').toString()).toBe(Decimal(123.45).toString());
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
it('exposes namespace helpers for min and max', () => {
|
|
1006
|
-
const nsMin = Decimal.min(Decimal(10), 3, null);
|
|
1007
|
-
const nsMax = Decimal.max(undefined, 7, Decimal(8));
|
|
1008
|
-
expect(nsMin?.toString()).toBe(Decimal(3).toString());
|
|
1009
|
-
expect(nsMax?.toString()).toBe(Decimal(8).toString());
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
1012
|
-
|
|
1013
|
-
describe('Decimal numeric accessors', () => {
|
|
1014
|
-
it('produces native number output', () => {
|
|
1015
|
-
const value = Decimal({ coeff: 314159n, digits: 5n });
|
|
1016
|
-
expect(value.number()).toBeCloseTo(3.14159);
|
|
1017
|
-
});
|
|
1018
|
-
|
|
1019
|
-
it('returns integer part as bigint', () => {
|
|
1020
|
-
const value = Decimal({ coeff: -98765n, digits: 2n });
|
|
1021
|
-
expect(value.integer()).toBe(-987n);
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
it('round trips non-terminating binary fractions', () => {
|
|
1025
|
-
const cases = [0.1, 0.2, 0.3, 0.1 + 0.2, 1 / 3, 0.615];
|
|
1026
|
-
for (const value of cases) {
|
|
1027
|
-
expect(Decimal(value).number()).toBe(value);
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
describe('Decimal string parsing', () => {
|
|
1033
|
-
it('accepts scientific notation strings', () => {
|
|
1034
|
-
expect(Decimal('1.5e2').toString()).toBe(Decimal(150).toString());
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
it('trims whitespace around strings', () => {
|
|
1038
|
-
expect(Decimal(' -2.5e1 ').toString()).toBe(Decimal(-25).toString());
|
|
1039
|
-
});
|
|
1040
|
-
|
|
1041
|
-
it('throws on invalid empty strings', () => {
|
|
1042
|
-
expect(() => Decimal('')).toThrow('Invalid number');
|
|
1043
|
-
});
|
|
1044
|
-
|
|
1045
|
-
it('accepts explicit positive prefixes', () => {
|
|
1046
|
-
expect(Decimal('+42.1').toString()).toBe(Decimal(42.1).toString());
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
|
-
it('throws when exponent lacks a mantissa', () => {
|
|
1050
|
-
expect(() => Decimal('e10')).toThrow('Invalid number');
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
it('throws on sign-only strings', () => {
|
|
1054
|
-
expect(() => Decimal('+')).toThrow('Invalid number');
|
|
1055
|
-
expect(() => Decimal('-')).toThrow('Invalid number');
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
it('parses values that start with a decimal point', () => {
|
|
1059
|
-
expect(Decimal('.5').toString()).toBe(Decimal(0.5).toString());
|
|
1060
|
-
});
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
describe('Decimal boundaries', () => {
|
|
1064
|
-
it('rejects NaN inputs', () => {
|
|
1065
|
-
expect(() => Decimal(Number.NaN)).toThrow('Invalid number');
|
|
1066
|
-
});
|
|
1067
|
-
|
|
1068
|
-
it('rejects malformed scientific notation', () => {
|
|
1069
|
-
expect(() => Decimal('1e')).toThrow('Invalid number');
|
|
1070
|
-
});
|
|
1071
|
-
|
|
1072
|
-
it('rejects unsupported input payloads', () => {
|
|
1073
|
-
expect(() => Decimal({} as unknown as never)).toThrow('Invalid input type for Decimal');
|
|
1074
|
-
});
|
|
1075
|
-
|
|
1076
|
-
it('throws when rounding with zero step', () => {
|
|
1077
|
-
expect(() => Decimal(5).roundBy(0)).toThrow('Cannot align to zero');
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
it('throws when modulo divisor is zero', () => {
|
|
1081
|
-
expect(() => Decimal(7).mod(0)).toThrow('Division by zero');
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
it('throws when clamp bounds are inverted', () => {
|
|
1085
|
-
expect(() => Decimal(1).clamp(5, 1)).toThrow('Invalid clamp range');
|
|
1086
|
-
});
|
|
1087
|
-
|
|
1088
|
-
it('throws when raising zero to a negative exponent', () => {
|
|
1089
|
-
expect(() => Decimal(0).pow(-1n, 6n)).toThrow('Zero to negative exponent is undefined');
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
it('treats negative digit counts as zero when powering', () => {
|
|
1093
|
-
const power = Decimal(2).pow(2n, -1n);
|
|
1094
|
-
expect(power.toString()).toBe(Decimal(4).toString());
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
it('throws when raising negative bases to fractional exponents', () => {
|
|
1098
|
-
expect(() => Decimal(-4).pow(Decimal({ coeff: 5n, digits: 1n }), 8n)).toThrow(
|
|
1099
|
-
'Fractional exponent requires non-negative base',
|
|
1100
|
-
);
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
it('returns one when exponent is zero', () => {
|
|
1104
|
-
const result = Decimal(7).pow(0n, 6n);
|
|
1105
|
-
expect(result.toString()).toBe(Decimal(1).toString());
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
it('handles positive-exponent integer parts when exponentiating', () => {
|
|
1109
|
-
const exponent = Decimal({ coeff: 12n, digits: -1n });
|
|
1110
|
-
const result = Decimal(2).pow(exponent, 20n);
|
|
1111
|
-
const expected = Decimal({ coeff: 1n << 120n, digits: 0n });
|
|
1112
|
-
expect(result.eq(expected)).toBe(true);
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
it('negates values without mutating the original', () => {
|
|
1116
|
-
const original = Decimal(3.5);
|
|
1117
|
-
const negated = original.neg();
|
|
1118
|
-
expect(negated.toString()).toBe(Decimal(-3.5).toString());
|
|
1119
|
-
expect(original.toString()).toBe(Decimal(3.5).toString());
|
|
1120
|
-
});
|
|
1121
|
-
|
|
1122
|
-
it('respects degree-one roots with rescaling', () => {
|
|
1123
|
-
const value = Decimal({ coeff: 1234000n, digits: 6n });
|
|
1124
|
-
const result = value.root(1n, 8n);
|
|
1125
|
-
expect(result.toString()).toBe(Decimal('1.23400000').toString());
|
|
1126
|
-
expect(result.digits).toBe(8n);
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
it('keeps scale when requesting coarser degree-one root precision', () => {
|
|
1130
|
-
const value = Decimal({ coeff: 1234000n, digits: 6n });
|
|
1131
|
-
const result = value.root(1n, 5n);
|
|
1132
|
-
expect(result.toString()).toBe(Decimal('1.234000').toString());
|
|
1133
|
-
expect(result.digits).toBe(6n);
|
|
1134
|
-
expect(value.digits).toBe(6n);
|
|
1135
|
-
});
|
|
1136
|
-
|
|
1137
|
-
it('retains requested digits when dividing with trailing zeros', () => {
|
|
1138
|
-
const result = Decimal(1).div(Decimal(2), 4n);
|
|
1139
|
-
expect(result.toString()).toBe(Decimal('0.5000').toString());
|
|
1140
|
-
expect(result.digits).toBe(4n);
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
it('retains requested digits when raising to negative exponents', () => {
|
|
1144
|
-
const result = Decimal(2).pow(-1n, 6n);
|
|
1145
|
-
expect(result.toString()).toBe(Decimal('0.500000').toString());
|
|
1146
|
-
expect(result.digits).toBe(6n);
|
|
1147
|
-
});
|
|
1148
|
-
|
|
1149
|
-
it('retains requested digits when taking roots with trailing zeros', () => {
|
|
1150
|
-
const result = Decimal('1.440000').root(2n, 6n);
|
|
1151
|
-
expect(result.toString()).toBe(Decimal('1.200000').toString());
|
|
1152
|
-
expect(result.digits).toBe(6n);
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
it('rescale compresses digits only when explicitly invoked', () => {
|
|
1156
|
-
const value = Decimal(1).div(2, 6n);
|
|
1157
|
-
const compressed = value.rescale();
|
|
1158
|
-
expect(value.digits).toBe(6n);
|
|
1159
|
-
expect(compressed.toString()).toBe(Decimal('0.5').toString());
|
|
1160
|
-
expect(compressed.digits).toBe(1n);
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
it('computes logarithms with fractional digits', () => {
|
|
1164
|
-
const result = Decimal(3).log(Decimal(2), 6n);
|
|
1165
|
-
expect(result.number()).toBeCloseTo(Math.log2(3), 5);
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
it('converts positive exponent decimals to strings without decimal points', () => {
|
|
1169
|
-
const value = Decimal({ coeff: 123n, digits: -2n });
|
|
1170
|
-
expect(value.toString()).toBe('12300');
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
it('returns integer values respecting positive exponents', () => {
|
|
1174
|
-
const value = Decimal({ coeff: 12n, digits: -2n });
|
|
1175
|
-
expect(value.integer()).toBe(1200n);
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
it('returns null when min and max receive no operands', () => {
|
|
1179
|
-
expect(min()).toBeNull();
|
|
1180
|
-
expect(max()).toBeNull();
|
|
1181
|
-
});
|
|
1182
|
-
|
|
1183
|
-
it('keeps values unchanged when already within clamp bounds', () => {
|
|
1184
|
-
const original = Decimal(3.5);
|
|
1185
|
-
const clamped = original.clamp(Decimal(0), Decimal(5));
|
|
1186
|
-
expect(clamped.eq(original)).toBe(true);
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
it('compresses zero to standard form with rescale', () => {
|
|
1190
|
-
const value = Decimal({ coeff: 0n, digits: 5n });
|
|
1191
|
-
const compressed = value.rescale();
|
|
1192
|
-
expect(compressed.digits).toBe(0n);
|
|
1193
|
-
expect(compressed.toString()).toBe('0');
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
it('compresses zero with negative exponent to standard form with rescale', () => {
|
|
1197
|
-
const value = Decimal({ coeff: 0n, digits: -5n });
|
|
1198
|
-
const compressed = value.rescale();
|
|
1199
|
-
expect(compressed.digits).toBe(0n);
|
|
1200
|
-
expect(compressed.toString()).toBe('0');
|
|
1201
|
-
});
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
describe('Decimal inverse operations', () => {
|
|
1205
|
-
it('computes multiplicative inverses without mutating the original', () => {
|
|
1206
|
-
const original = Decimal('4');
|
|
1207
|
-
const inverse = original.inverse(4n);
|
|
1208
|
-
expect(inverse.toString()).toBe('0.2500');
|
|
1209
|
-
expect(original.toString()).toBe('4');
|
|
1210
|
-
});
|
|
1211
|
-
|
|
1212
|
-
it('inverts in place with inverse$', () => {
|
|
1213
|
-
const value = Decimal('8');
|
|
1214
|
-
value.inverse$(3n);
|
|
1215
|
-
expect(value.toString()).toBe('0.125');
|
|
1216
|
-
});
|
|
1217
|
-
|
|
1218
|
-
it('throws when inverting zero in place', () => {
|
|
1219
|
-
const zero = Decimal(0);
|
|
1220
|
-
expect(() => zero.inverse$()).toThrow('Division by zero');
|
|
1221
|
-
});
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
describe('Decimal primitive integrations', () => {
|
|
1225
|
-
it('uses Symbol.toString for string coercion', () => {
|
|
1226
|
-
const value = Decimal('-12.5') as any;
|
|
1227
|
-
const primitive = value.toString();
|
|
1228
|
-
expect(primitive).toBe('-12.5');
|
|
1229
|
-
});
|
|
1230
|
-
|
|
1231
|
-
it('formats inspect output based on color hint', () => {
|
|
1232
|
-
const toInspect = Decimal('3.14') as any;
|
|
1233
|
-
const inspectedWithColor = toInspect[Symbol.for('nodejs.util.inspect.custom')](0, { colors: true });
|
|
1234
|
-
const inspectedWithoutColor = toInspect[Symbol.for('nodejs.util.inspect.custom')](0, { colors: false });
|
|
1235
|
-
expect(inspectedWithColor).toContain('\u001B[33m3.14\u001B[m');
|
|
1236
|
-
expect(inspectedWithColor).toContain('(314 * 10 ** -2)');
|
|
1237
|
-
expect(inspectedWithoutColor).toBe('3.14');
|
|
1238
|
-
});
|
|
1239
|
-
});
|