Merge pull request #217 from CosmWasm/214-decimal-comparisons

Add comparison methods to Decimal class
This commit is contained in:
Simon Warta 2020-06-10 16:50:08 +02:00 committed by GitHub
commit bb3cd1352a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 243 additions and 3 deletions

View File

@ -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 {

View File

@ -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",

View File

@ -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);
});
});
});

View File

@ -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;
}
}

View File

@ -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;
}