diff --git a/packages/faucet/src/tokenmanager.ts b/packages/faucet/src/tokenmanager.ts index 88996eee..9d09ad76 100644 --- a/packages/faucet/src/tokenmanager.ts +++ b/packages/faucet/src/tokenmanager.ts @@ -54,9 +54,7 @@ export class TokenManager { const thresholdAmount = this.refillThreshold(tickerSymbol); const threshold = Decimal.fromAtomics(thresholdAmount.amount, meta.fractionalDigits); - // TODO: perform < operation on Decimal type directly - // https://github.com/iov-one/iov-core/issues/1375 - return balance.toFloatApproximation() < threshold.toFloatApproximation(); + return balance.isLessThan(threshold); } private getTokenMeta(tickerSymbol: string): BankTokenMeta { diff --git a/packages/math/package.json b/packages/math/package.json index fb4cec28..b3125f61 100644 --- a/packages/math/package.json +++ b/packages/math/package.json @@ -25,6 +25,7 @@ "scripts": { "docs": "shx rm -rf docs && typedoc --options typedoc.js", "lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"", + "lint-fix": "eslint --max-warnings 0 \"**/*.{js,ts}\" --fix", "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", "format-text": "prettier --write --prose-wrap always --print-width 80 \"./*.md\"", "test-node": "node jasmine-testrunner.js", diff --git a/packages/math/src/decimal.spec.ts b/packages/math/src/decimal.spec.ts index f0d6f1d3..b0e1803b 100644 --- a/packages/math/src/decimal.spec.ts +++ b/packages/math/src/decimal.spec.ts @@ -210,4 +210,214 @@ describe("Decimal", () => { expect(() => zero.plus(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); }); }); + + describe("equals", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.equals(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.equals(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.equals(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.equals(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.equals(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.equals(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.equals(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.equals(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.equals(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isLessThan", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(zero.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(zero.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(one.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(one.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(one.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("1.5", 5))).toEqual(false); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(oneDotFive.isLessThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isLessThan(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isLessThanOrEqual", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(zero.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(one.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(true); + expect(oneDotFive.isLessThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isLessThanOrEqual(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isGreaterThan", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(one.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("1.5", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.isGreaterThan(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThan(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); + + describe("isGreaterThanOrEqual", () => { + it("returns correct values", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(zero.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(false); + + const one = Decimal.fromUserInput("1", 5); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(one.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + const oneDotFive = Decimal.fromUserInput("1.5", 5); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("0", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("1", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("1.5", 5))).toEqual(true); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("2", 5))).toEqual(false); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("2.8", 5))).toEqual(false); + expect(oneDotFive.isGreaterThanOrEqual(Decimal.fromUserInput("0.12345", 5))).toEqual(true); + + // original value remain unchanged + expect(zero.toString()).toEqual("0"); + expect(one.toString()).toEqual("1"); + expect(oneDotFive.toString()).toEqual("1.5"); + }); + + it("throws for different fractional digits", () => { + const zero = Decimal.fromUserInput("0", 5); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i); + + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i); + expect(() => zero.isGreaterThanOrEqual(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i); + }); + }); }); diff --git a/packages/math/src/decimal.ts b/packages/math/src/decimal.ts index e126e4ed..ffedd0ed 100644 --- a/packages/math/src/decimal.ts +++ b/packages/math/src/decimal.ts @@ -64,6 +64,11 @@ export class Decimal { } } + public static compare(a: Decimal, b: Decimal): number { + if (a.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match"); + return a.data.atomics.cmp(new BN(b.atomics)); + } + public get atomics(): string { return this.data.atomics.toString(); } @@ -118,4 +123,24 @@ export class Decimal { const sum = this.data.atomics.add(new BN(b.atomics)); return new Decimal(sum.toString(), this.fractionalDigits); } + + public equals(b: Decimal): boolean { + return Decimal.compare(this, b) === 0; + } + + public isLessThan(b: Decimal): boolean { + return Decimal.compare(this, b) < 0; + } + + public isLessThanOrEqual(b: Decimal): boolean { + return Decimal.compare(this, b) <= 0; + } + + public isGreaterThan(b: Decimal): boolean { + return Decimal.compare(this, b) > 0; + } + + public isGreaterThanOrEqual(b: Decimal): boolean { + return Decimal.compare(this, b) >= 0; + } } diff --git a/packages/math/types/decimal.d.ts b/packages/math/types/decimal.d.ts index 9d65be9d..0642fa07 100644 --- a/packages/math/types/decimal.d.ts +++ b/packages/math/types/decimal.d.ts @@ -7,6 +7,7 @@ export declare class Decimal { static fromUserInput(input: string, fractionalDigits: number): Decimal; static fromAtomics(atomics: string, fractionalDigits: number): Decimal; private static verifyFractionalDigits; + static compare(a: Decimal, b: Decimal): number; get atomics(): string; get fractionalDigits(): number; private readonly data; @@ -23,4 +24,9 @@ export declare class Decimal { * Both values need to have the same fractional digits. */ plus(b: Decimal): Decimal; + equals(b: Decimal): boolean; + isLessThan(b: Decimal): boolean; + isLessThanOrEqual(b: Decimal): boolean; + isGreaterThan(b: Decimal): boolean; + isGreaterThanOrEqual(b: Decimal): boolean; }