@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 CHANGED
@@ -6,7 +6,7 @@
6
6
  "arbitrary",
7
7
  "bigint"
8
8
  ],
9
- "version": "0.1.0-alpha.1",
9
+ "version": "0.1.0-alpha.2",
10
10
  "type": "module",
11
11
  "main": "./src/index.ts",
12
12
  "author": "kikuchan <kikuchan98@gmail.com>",
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
- this.coeff = -this.coeff;
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
  }
@@ -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
  });