@kikuchan/decimal 0.1.0-alpha.1 → 0.1.0-alpha.2
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/package.json +1 -1
- package/src/index.ts +21 -7
- package/tests/decimal.spec.ts +91 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -41,10 +41,12 @@ export interface Decimal {
|
|
|
41
41
|
splitBy(step: DecimalLike, mode?: RoundingMode): [Decimal, Decimal];
|
|
42
42
|
|
|
43
43
|
// Sign and absolute
|
|
44
|
-
neg$(): this;
|
|
45
|
-
neg(): Decimal;
|
|
44
|
+
neg$(flag?: boolean): this;
|
|
45
|
+
neg(flag?: boolean): Decimal;
|
|
46
46
|
abs$(): this;
|
|
47
47
|
abs(): Decimal;
|
|
48
|
+
sign$(): this;
|
|
49
|
+
sign(): Decimal;
|
|
48
50
|
isZero(): boolean;
|
|
49
51
|
isPositive(): boolean;
|
|
50
52
|
isNegative(): boolean;
|
|
@@ -519,13 +521,15 @@ class DecimalImpl implements Decimal {
|
|
|
519
521
|
return this.clone().splitBy$(step, mode);
|
|
520
522
|
}
|
|
521
523
|
|
|
522
|
-
neg$(): this {
|
|
523
|
-
|
|
524
|
+
neg$(flag?: boolean): this {
|
|
525
|
+
if (flag !== false) {
|
|
526
|
+
this.coeff = -this.coeff;
|
|
527
|
+
}
|
|
524
528
|
return this;
|
|
525
529
|
}
|
|
526
530
|
|
|
527
|
-
neg(): DecimalImpl {
|
|
528
|
-
return this.clone().neg$();
|
|
531
|
+
neg(flag?: boolean): DecimalImpl {
|
|
532
|
+
return this.clone().neg$(flag);
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
isZero(): boolean {
|
|
@@ -864,6 +868,17 @@ class DecimalImpl implements Decimal {
|
|
|
864
868
|
return this.clone().log$(base, digits);
|
|
865
869
|
}
|
|
866
870
|
|
|
871
|
+
sign$() {
|
|
872
|
+
if (this.isZero()) return this;
|
|
873
|
+
this.coeff = this.coeff < 0n ? -1n : 1n;
|
|
874
|
+
this.digits = 0n;
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
sign() {
|
|
879
|
+
return this.clone().sign$();
|
|
880
|
+
}
|
|
881
|
+
|
|
867
882
|
order(): bigint {
|
|
868
883
|
if (this.isZero()) throw new RangeError('order undefined for 0');
|
|
869
884
|
return BigInt(abs(this.coeff).toString().length) - 1n - this.digits;
|
|
@@ -986,7 +1001,6 @@ export namespace Decimal {
|
|
|
986
1001
|
return minmax(...values)[0];
|
|
987
1002
|
}
|
|
988
1003
|
|
|
989
|
-
/* c8 ignore next */
|
|
990
1004
|
export function max(...values: (DecimalLike | null | undefined)[]): Decimal | null {
|
|
991
1005
|
return minmax(...values)[1];
|
|
992
1006
|
}
|
package/tests/decimal.spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { RoundingMode } from '../src/index.ts';
|
|
3
3
|
import { Decimal, isDecimal, max, min, minmax, pow10 } from '../src/index.ts';
|
|
4
4
|
|
|
@@ -32,6 +32,11 @@ describe('Decimal construction', () => {
|
|
|
32
32
|
expect(value.digits).toBe(5n);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it('returns existing decimal instances unchanged', () => {
|
|
36
|
+
const value = Decimal(42);
|
|
37
|
+
expect(Decimal(value)).toBe(value);
|
|
38
|
+
});
|
|
39
|
+
|
|
35
40
|
it('returns nullish inputs unchanged', () => {
|
|
36
41
|
expect(Decimal(null)).toBeNull();
|
|
37
42
|
expect(Decimal(undefined)).toBeUndefined();
|
|
@@ -261,6 +266,12 @@ describe('Decimal transforms', () => {
|
|
|
261
266
|
});
|
|
262
267
|
|
|
263
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
|
+
|
|
264
275
|
it('recognizes supported literal inputs', () => {
|
|
265
276
|
expect(Decimal.isDecimalLike('42.00')).toBe(true);
|
|
266
277
|
expect(Decimal.isDecimalLike(42)).toBe(true);
|
|
@@ -367,6 +378,32 @@ describe('Decimal sign helpers', () => {
|
|
|
367
378
|
expect(negative.isPositive()).toBe(false);
|
|
368
379
|
expect(negative.isNegative()).toBe(true);
|
|
369
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
|
+
});
|
|
370
407
|
});
|
|
371
408
|
|
|
372
409
|
describe('Decimal truncation', () => {
|
|
@@ -647,6 +684,14 @@ describe('Decimal roots', () => {
|
|
|
647
684
|
expect(root.toString()).toBe('-3.00000000');
|
|
648
685
|
});
|
|
649
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
|
+
|
|
650
695
|
it('throws on even roots of negative numbers', () => {
|
|
651
696
|
expect(() => Decimal(-16).root(2n, 8n)).toThrowError();
|
|
652
697
|
});
|
|
@@ -714,6 +759,46 @@ describe('Decimal roots', () => {
|
|
|
714
759
|
const tolerance = pow10(-(digits - 4n));
|
|
715
760
|
expect(recomposed.isCloseTo(value.rescale(digits), tolerance)).toBe(true);
|
|
716
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
|
+
});
|
|
717
802
|
});
|
|
718
803
|
|
|
719
804
|
describe('Decimal modular helpers', () => {
|
|
@@ -726,6 +811,11 @@ describe('Decimal modular helpers', () => {
|
|
|
726
811
|
expect(Decimal(-17).modPositive(5).toString()).toBe(Decimal(3).toString());
|
|
727
812
|
});
|
|
728
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
|
+
|
|
729
819
|
it('rejects negative divisors for positive modulo', () => {
|
|
730
820
|
expect(() => Decimal(1).modPositive(-5)).toThrow('Modulo divisor must be positive');
|
|
731
821
|
});
|