Skip to content

Commit

Permalink
math: Change Decimal.multiply to operate on Uint classes
Browse files Browse the repository at this point in the history
  • Loading branch information
willclarktech committed Aug 19, 2020
1 parent 3af1098 commit a737df0
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 33 deletions.
5 changes: 2 additions & 3 deletions packages/launchpad/src/gas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Decimal } from "@cosmjs/math";
import { Decimal, Uint53 } from "@cosmjs/math";

import { coins } from "./coins";
import { StdFee } from "./types";
Expand Down Expand Up @@ -34,8 +34,7 @@ export type GasLimits<T extends Record<string, StdFee>> = {
};

function calculateFee(gasLimit: number, { denom, amount: gasPriceAmount }: GasPrice): StdFee {
const gasLimitDecimal = Decimal.fromUserInput(gasLimit.toString(), gasPriceAmount.fractionalDigits);
const amount = Math.ceil(gasPriceAmount.multiply(gasLimitDecimal).toFloatApproximation());
const amount = Math.ceil(gasPriceAmount.multiply(new Uint53(gasLimit)).toFloatApproximation());
return {
amount: coins(amount, denom),
gas: gasLimit.toString(),
Expand Down
87 changes: 64 additions & 23 deletions packages/math/src/decimal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Decimal } from "./decimal";
import { Uint32, Uint53, Uint64 } from "./integers";

describe("Decimal", () => {
describe("fromAtomics", () => {
Expand Down Expand Up @@ -212,43 +213,83 @@ describe("Decimal", () => {
});

describe("multiply", () => {
it("returns correct values", () => {
it("returns correct values for Uint32", () => {
const zero = Decimal.fromUserInput("0", 5);
expect(zero.multiply(Decimal.fromUserInput("0", 5)).toString()).toEqual("0");
expect(zero.multiply(Decimal.fromUserInput("1", 5)).toString()).toEqual("0");
expect(zero.multiply(Decimal.fromUserInput("2", 5)).toString()).toEqual("0");
expect(zero.multiply(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("0");
expect(zero.multiply(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("0");
expect(zero.multiply(new Uint32(0)).toString()).toEqual("0");
expect(zero.multiply(new Uint32(1)).toString()).toEqual("0");
expect(zero.multiply(new Uint32(2)).toString()).toEqual("0");
expect(zero.multiply(new Uint32(4294967295)).toString()).toEqual("0");

const one = Decimal.fromUserInput("1", 5);
expect(one.multiply(Decimal.fromUserInput("0", 5)).toString()).toEqual("0");
expect(one.multiply(Decimal.fromUserInput("1", 5)).toString()).toEqual("1");
expect(one.multiply(Decimal.fromUserInput("2", 5)).toString()).toEqual("2");
expect(one.multiply(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("2.8");
expect(one.multiply(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("0.12345");
expect(one.multiply(new Uint32(0)).toString()).toEqual("0");
expect(one.multiply(new Uint32(1)).toString()).toEqual("1");
expect(one.multiply(new Uint32(2)).toString()).toEqual("2");
expect(one.multiply(new Uint32(4294967295)).toString()).toEqual("4294967295");

const oneDotFive = Decimal.fromUserInput("1.5", 5);
expect(oneDotFive.multiply(Decimal.fromUserInput("0", 5)).toString()).toEqual("0");
expect(oneDotFive.multiply(Decimal.fromUserInput("1", 5)).toString()).toEqual("1.5");
expect(oneDotFive.multiply(Decimal.fromUserInput("2", 5)).toString()).toEqual("3");
expect(oneDotFive.multiply(Decimal.fromUserInput("2.8", 5)).toString()).toEqual("4.2");
expect(oneDotFive.multiply(Decimal.fromUserInput("0.12345", 5)).toString()).toEqual("0.18517");
expect(oneDotFive.multiply(new Uint32(0)).toString()).toEqual("0");
expect(oneDotFive.multiply(new Uint32(1)).toString()).toEqual("1.5");
expect(oneDotFive.multiply(new Uint32(2)).toString()).toEqual("3");
expect(oneDotFive.multiply(new Uint32(4294967295)).toString()).toEqual("6442450942.5");

// 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", () => {
it("returns correct values for Uint53", () => {
const zero = Decimal.fromUserInput("0", 5);
expect(zero.multiply(new Uint53(0)).toString()).toEqual("0");
expect(zero.multiply(new Uint53(1)).toString()).toEqual("0");
expect(zero.multiply(new Uint53(2)).toString()).toEqual("0");
expect(zero.multiply(new Uint53(9007199254740991)).toString()).toEqual("0");

const one = Decimal.fromUserInput("1", 5);
expect(one.multiply(new Uint53(0)).toString()).toEqual("0");
expect(one.multiply(new Uint53(1)).toString()).toEqual("1");
expect(one.multiply(new Uint53(2)).toString()).toEqual("2");
expect(one.multiply(new Uint53(9007199254740991)).toString()).toEqual("9007199254740991");

const oneDotFive = Decimal.fromUserInput("1.5", 5);
expect(oneDotFive.multiply(new Uint53(0)).toString()).toEqual("0");
expect(oneDotFive.multiply(new Uint53(1)).toString()).toEqual("1.5");
expect(oneDotFive.multiply(new Uint53(2)).toString()).toEqual("3");
expect(oneDotFive.multiply(new Uint53(9007199254740991)).toString()).toEqual("13510798882111486.5");

// original value remain unchanged
expect(zero.toString()).toEqual("0");
expect(one.toString()).toEqual("1");
expect(oneDotFive.toString()).toEqual("1.5");
});

it("returns correct values for Uint64", () => {
const zero = Decimal.fromUserInput("0", 5);
expect(() => zero.multiply(Decimal.fromUserInput("1", 1))).toThrowError(/do not match/i);
expect(() => zero.multiply(Decimal.fromUserInput("1", 2))).toThrowError(/do not match/i);
expect(() => zero.multiply(Decimal.fromUserInput("1", 3))).toThrowError(/do not match/i);
expect(() => zero.multiply(Decimal.fromUserInput("1", 4))).toThrowError(/do not match/i);
expect(zero.multiply(Uint64.fromString("0")).toString()).toEqual("0");
expect(zero.multiply(Uint64.fromString("1")).toString()).toEqual("0");
expect(zero.multiply(Uint64.fromString("2")).toString()).toEqual("0");
expect(zero.multiply(Uint64.fromString("18446744073709551615")).toString()).toEqual("0");

const one = Decimal.fromUserInput("1", 5);
expect(one.multiply(Uint64.fromString("0")).toString()).toEqual("0");
expect(one.multiply(Uint64.fromString("1")).toString()).toEqual("1");
expect(one.multiply(Uint64.fromString("2")).toString()).toEqual("2");
expect(one.multiply(Uint64.fromString("18446744073709551615")).toString()).toEqual(
"18446744073709551615",
);

const oneDotFive = Decimal.fromUserInput("1.5", 5);
expect(oneDotFive.multiply(Uint64.fromString("0")).toString()).toEqual("0");
expect(oneDotFive.multiply(Uint64.fromString("1")).toString()).toEqual("1.5");
expect(oneDotFive.multiply(Uint64.fromString("2")).toString()).toEqual("3");
expect(oneDotFive.multiply(Uint64.fromString("18446744073709551615")).toString()).toEqual(
"27670116110564327422.5",
);

expect(() => zero.multiply(Decimal.fromUserInput("1", 6))).toThrowError(/do not match/i);
expect(() => zero.multiply(Decimal.fromUserInput("1", 7))).toThrowError(/do not match/i);
// original value remain unchanged
expect(zero.toString()).toEqual("0");
expect(one.toString()).toEqual("1");
expect(oneDotFive.toString()).toEqual("1.5");
});
});

Expand Down
10 changes: 5 additions & 5 deletions packages/math/src/decimal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import BN from "bn.js";

import { Uint32, Uint53, Uint64 } from "./integers";

// Too large values lead to massive memory usage. Limit to something sensible.
// The largest value we need is 18 (Ether).
const maxFractionalDigits = 100;
Expand Down Expand Up @@ -127,12 +129,10 @@ export class Decimal {
/**
* a.multiply(b) returns a*b.
*
* Both values need to have the same fractional digits.
* We only allow multiplication by unsigned integers to avoid rounding errors.
*/
public multiply(b: Decimal): Decimal {
if (this.fractionalDigits !== b.fractionalDigits) throw new Error("Fractional digits do not match");
const factor = new BN(10).pow(new BN(this.data.fractionalDigits));
const product = this.data.atomics.mul(new BN(b.atomics)).div(factor);
public multiply(b: Uint32 | Uint53 | Uint64): Decimal {
const product = this.data.atomics.mul(new BN(b.toString()));
return new Decimal(product.toString(), this.fractionalDigits);
}

Expand Down
5 changes: 3 additions & 2 deletions packages/math/types/decimal.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Uint32, Uint53, Uint64 } from "./integers";
/**
* A type for arbitrary precision, non-negative decimals.
*
Expand Down Expand Up @@ -27,9 +28,9 @@ export declare class Decimal {
/**
* a.multiply(b) returns a*b.
*
* Both values need to have the same fractional digits.
* We only allow multiplication by unsigned integers to avoid rounding errors.
*/
multiply(b: Decimal): Decimal;
multiply(b: Uint32 | Uint53 | Uint64): Decimal;
equals(b: Decimal): boolean;
isLessThan(b: Decimal): boolean;
isLessThanOrEqual(b: Decimal): boolean;
Expand Down

0 comments on commit a737df0

Please sign in to comment.