Skip to content

Commit ed46c38

Browse files
authored
Merge pull request #1921 from cosmos/fromAtomics-bigint
Let Decimal.fromAtomics accept `string | bigint`
2 parents 30b0a41 + b80413e commit ed46c38

File tree

3 files changed

+66
-12
lines changed

3 files changed

+66
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ and this project adheres to
9898
`Secp256k1.verifySignature`/`.createSignature`/`.makeKeypair` synchronous and
9999
let them not return a Promise.
100100
- @cosmjs/cosmwasm-stargate: Rename package to @cosmjs/cosmwasm. ([#1903])
101+
- @cosmjs/math: `Decimal.fromAtomics` now accepts atomics as `string | bigint`
102+
such that you can pass in BigInts directly. This is more performant than going
103+
through strings in cases where you have a BitInt already. Strings remain
104+
supported for convenient usage with coins.
101105

102106
[#1883]: https://github.com/cosmos/cosmjs/issues/1883
103107
[#1866]: https://github.com/cosmos/cosmjs/issues/1866

packages/math/src/decimal.spec.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { Uint32, Uint53, Uint64 } from "./integers";
33

44
describe("Decimal", () => {
55
describe("fromAtomics", () => {
6-
it("leads to correct atomics value", () => {
6+
it("leads to correct atomics value (string)", () => {
77
expect(Decimal.fromAtomics("1", 0).atomics).toEqual("1");
88
expect(Decimal.fromAtomics("1", 1).atomics).toEqual("1");
99
expect(Decimal.fromAtomics("1", 2).atomics).toEqual("1");
1010

11+
expect(Decimal.fromAtomics("0", 5).atomics).toEqual("0");
1112
expect(Decimal.fromAtomics("1", 5).atomics).toEqual("1");
1213
expect(Decimal.fromAtomics("2", 5).atomics).toEqual("2");
1314
expect(Decimal.fromAtomics("3", 5).atomics).toEqual("3");
@@ -24,24 +25,51 @@ describe("Decimal", () => {
2425
expect(Decimal.fromAtomics("00044", 5).atomics).toEqual("44");
2526
});
2627

28+
it("leads to correct atomics value (bigint)", () => {
29+
expect(Decimal.fromAtomics(1n, 0).atomics).toEqual("1");
30+
expect(Decimal.fromAtomics(1n, 1).atomics).toEqual("1");
31+
expect(Decimal.fromAtomics(1n, 2).atomics).toEqual("1");
32+
33+
expect(Decimal.fromAtomics(0n, 5).atomics).toEqual("0");
34+
expect(Decimal.fromAtomics(1n, 5).atomics).toEqual("1");
35+
expect(Decimal.fromAtomics(2n, 5).atomics).toEqual("2");
36+
expect(Decimal.fromAtomics(3n, 5).atomics).toEqual("3");
37+
expect(Decimal.fromAtomics(10n, 5).atomics).toEqual("10");
38+
expect(Decimal.fromAtomics(20n, 5).atomics).toEqual("20");
39+
expect(Decimal.fromAtomics(30n, 5).atomics).toEqual("30");
40+
expect(Decimal.fromAtomics(100000000000000000000000n, 5).atomics).toEqual("100000000000000000000000");
41+
expect(Decimal.fromAtomics(200000000000000000000000n, 5).atomics).toEqual("200000000000000000000000");
42+
expect(Decimal.fromAtomics(300000000000000000000000n, 5).atomics).toEqual("300000000000000000000000");
43+
});
44+
2745
it("reads fractional digits correctly", () => {
2846
expect(Decimal.fromAtomics("44", 0).toString()).toEqual("44");
2947
expect(Decimal.fromAtomics("44", 1).toString()).toEqual("4.4");
3048
expect(Decimal.fromAtomics("44", 2).toString()).toEqual("0.44");
3149
expect(Decimal.fromAtomics("44", 3).toString()).toEqual("0.044");
3250
expect(Decimal.fromAtomics("44", 4).toString()).toEqual("0.0044");
51+
expect(Decimal.fromAtomics(44n, 0).toString()).toEqual("44");
52+
expect(Decimal.fromAtomics(44n, 1).toString()).toEqual("4.4");
53+
expect(Decimal.fromAtomics(44n, 2).toString()).toEqual("0.44");
54+
expect(Decimal.fromAtomics(44n, 3).toString()).toEqual("0.044");
55+
expect(Decimal.fromAtomics(44n, 4).toString()).toEqual("0.0044");
3356
});
3457

3558
it("throws for atomics that are not non-negative integers", () => {
3659
expect(() => Decimal.fromAtomics("0xAA", 0)).toThrowError(
37-
"Invalid string format. Only non-negative integers in decimal representation supported.",
60+
"Invalid string format. Only integers in decimal representation supported.",
3861
);
3962
expect(() => Decimal.fromAtomics("", 0)).toThrowError(
40-
"Invalid string format. Only non-negative integers in decimal representation supported.",
63+
"Invalid string format. Only integers in decimal representation supported.",
4164
);
42-
expect(() => Decimal.fromAtomics("-1", 0)).toThrowError(
43-
"Invalid string format. Only non-negative integers in decimal representation supported.",
65+
expect(() => Decimal.fromAtomics("--2", 0)).toThrowError(
66+
"Invalid string format. Only integers in decimal representation supported.",
4467
);
68+
expect(() => Decimal.fromAtomics("0.7", 0)).toThrowError(
69+
"Invalid string format. Only integers in decimal representation supported.",
70+
);
71+
72+
expect(() => Decimal.fromAtomics("-1", 0)).toThrowError("Only non-negative values supported.");
4573
});
4674
});
4775

packages/math/src/decimal.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,39 @@ export class Decimal {
5151

5252
const quantity = BigInt(`${whole}${fractional.padEnd(fractionalDigits, "0")}`);
5353

54+
// We can remove this restriction, but then need to test and update arithmetic operations.
55+
// See also https://github.com/cosmos/cosmjs/issues/1897
56+
if (quantity < 0n) throw new Error("Only non-negative values supported.");
57+
5458
return new Decimal(quantity, fractionalDigits);
5559
}
5660

57-
public static fromAtomics(atomics: string, fractionalDigits: number): Decimal {
58-
Decimal.verifyFractionalDigits(fractionalDigits);
59-
if (!atomics.match(/^[0-9]+$/)) {
60-
throw new Error(
61-
"Invalid string format. Only non-negative integers in decimal representation supported.",
62-
);
61+
/**
62+
* Constructs a decimal given the atomic units and the fractional digits.
63+
*
64+
* Atomics units are the smallest unit you operate with.
65+
* E.g. for EUR this could be Euro cents and for BTC this would be Satishi.
66+
*
67+
* To create the decimal value 12.60 (EUR) you would use atomics=1260, fractionalDigits=2.
68+
* To create the decimal value 3.4 (BTC) you would use atomics=340000000, fractionalDigits=8.
69+
*
70+
* In order to perform arithmetic operations on Decimal, all values must have the same `fractionalDigits` value.
71+
* So this should be fixed once per currency, not different per value.
72+
*/
73+
public static fromAtomics(atomics: string | bigint, fractionalDigits: number): Decimal {
74+
if (typeof atomics === "string") {
75+
if (!atomics.match(/^-?[0-9]+$/)) {
76+
throw new Error("Invalid string format. Only integers in decimal representation supported.");
77+
}
78+
return Decimal.fromAtomics(BigInt(atomics), fractionalDigits);
6379
}
64-
return new Decimal(BigInt(atomics), fractionalDigits);
80+
81+
// We can remove this restriction, but then need to test and update arithmetic operations.
82+
// See also https://github.com/cosmos/cosmjs/issues/1897
83+
if (atomics < 0n) throw new Error("Only non-negative values supported.");
84+
85+
Decimal.verifyFractionalDigits(fractionalDigits);
86+
return new Decimal(atomics, fractionalDigits);
6587
}
6688

6789
/**

0 commit comments

Comments
 (0)