Wondering what’s next for npm?Check out our public roadmap! »

    moneysafe

    2.2.1 • Public • Published

    Money$afe

    Convenient, safe money calculations in JS.

    Status - Developer Preview

    Money$afe has not yet been tested in production at scale.

    What is it?

    Writing software that deals with money is a bit of a pain in JavaScript. Money-safe calculations are harder than they should be.

    Why? Because JavaScript Numbers are IEEE 754 64-bit floating point. The result is that we can't safely add money because the decimal will get skewered by floating point rounding errors.

    .2 + .1 === .3; // false

    However, this problem effectively goes away if you perform the same calculations in arbitrary precision units. Money$afe converts your dollar values into BigNumbers and then exposes arithetic operations like add, multiply, and divide.

    With Money$afe:

    add($(.1), $(.2)).toNumber() === $(.3).toNumber();

    Even better. There's a convenient ledger form for common calculations like shopping carts:

    $$(
      $(40),
      $(60),
      // subtract discount
      subtractPercent(20),
      // add tax
      addPercent(10)
    ).toNumber(); // 88

    Known Issues

    This code was written in ES6, and no attempt has been made to compile it for older browsers. This should not be a problem in any modern evergreen browser, but it will likely throw errors in old IE and old versions of Safari. You have been warned. If you want to run the code in other browsers, you'll need to compile to ES5 yourself.

    Values are stored in arbitrary precision using BigNumber, so you can perform accurate calculations for cryptocurrencies such as Bitcoin or Ethereum which have 8 and 18 decimal precision, respectively. By way of contrast, JavaScript's native number type is IEEE 754 with 16 digits of decimal precision.

    To recap:

    • By default, all math operations automatically use arbitrary precision big numbers internally.
    • You can get the value in a number type using .toNumber().

    Getting Started

    Install moneysafe:

    npm install --save moneysafe

    Import the functions you need:

    import { $ } from 'moneysafe';
    import { $$, subtractPercent, addPercent } from 'moneysafe/ledger';

    OR:

    const { $ } = require('moneysafe');
    const { $$, subtractPercent, addPercent } = require('moneysafe/ledger');

    Enjoy:

    $$(
      $(40),
      $(60),
      // subtract discount
      subtractPercent(20),
      // add tax
      addPercent(10)
    ).toNumber(); // 88

    How does Money$afe work?

    It works by storing and acting on the amounts in cents instead of dollars, which reduces the floating point rounding errors you get when you represent them as decimal dollars. Of course, you'll still get rounding errors with lots of multiplication and division, but errors are less common and less significant when scaled to cents.

    $(dollars) => Money

    The $() factory takes a value in dollars and lifts it into the money object type.

    $(dollars: n) => Money

    Example:

    $(20).cents; // 2000

    Once a value is represented as money, you can operate on it using normal JavaScript operators. The resulting value will be in cents:

    $(20) / 2; // 1000 cents

    in$ Utility

    Take a numerical value in cents and convert to a numerical value in dollars, rounded to the nearest cent.

    in$(cents: n) => dollars: Number

    Since Money$afe allows you to use normal math operators, which work in cents, in$() is a convenient way to convert the result back to dollars:

    import { in$, $ } from 'moneysafe';
     
    in$($(20) / 2) // 10

    $ Static Props

    $.of()

    Takes a value and lifts it into the Money object type. Not rounded.

    $.of(amount) => Money

    Example:

    $.of(20.2).valueOf(); // 20.2
    $.of(1.635).valueOf(); // 1.635
    $.of(.1 + .2).valueOf(); // 0.30000000000000004

    The Money Type

    The Money type is a function object returned by the createCurrency factory. The type itself is a function that takes an amount in number or string format and returns a new Money object.

    money(amount) => Money

    Example:

    const a = $(20);
    const b = $(10);
     
    const c = a(b);
    console.log(+c); // 30

    The result is that standard function composition acts like addition. The following are equivalent:

    import pipe from 'lodash.flow';
    import { $ } from 'moneysafe';
     
    {
      const a = $(20);
      const b = $(10);
     
      const c = a(b);
      console.log(+c); // 30
    }
     
    {
      const c = pipe(
        $(20),
        $(10)
      )($(0));
     
      console.log(+c);
    }

    This is what makes the handy ledger syntax possible. $$ is just a thin wrapper around a standard function composition:

    import { $$, subtractPercent, addPercent } from 'moneysafe/ledger';
     
    +$$(
      $(40),
      $(60),
      // subtract discount
      subtractPercent(20),
      // add tax
      addPercent(10)
    ) // 88

    money.add()

    Takes an amount and returns a money instance with the sum of the stored value and the amount.

    money.add(amount: Money) => Money

    Example:

    $(10).add($(5)).toNumber() // 15

    money.minus()

    Takes an amount and returns a money instance with the difference between the stored value and the amount.

    money.minus(amount: Money) => Money

    Example:

    $(10).minus($(5)).toNumber() // 5
    $(10).minus(500).toNumber() // 5
    $(0).minus($(5)).toNumber() // -5

    money.toNumber(), money.valueOf()

    Convert a Money object to JavaScript Number format (IEEE 754 floating point). Note: JavaScript number precision is limited to 16 decimal digits.

    money.toNumber() => Number

    Example:

    $(2000).toNumber(); // 2000

    money.abs()

    Returns a Money object which contains the absolute value.

    money.abs() => Money

    Example:

    $('-8').abs().toString() === $('8').toString(); // true

    money.toString()

    Convert a Money object to a String. Warning: This isn't a properly localized currency string suitable for display to users. Please use a good i18n library and/or exchange rate API to convert to localized currency.

    money.toString() => String

    Example:

    $(2000).toString(); // "2000"

    money.map()

    Apply a function of type BigNumber => BigNumber in the context of the Money object. This allows you to implement arbitrary operations for Money objects, which you can apply by mapping them. Note: money.map() obeys the functor laws.

    money.map(f: BigNumber => BigNumber) => Money

    Example:

    const pow = exp => m => Array.from(
      { length: exp }, x => m
    ).reduce((a, b) => a.times(b));
     
    +$(2).map(pow(2)); // 4

    Utility functions

    add()

    Take any number of money objects and return the sum.

    add(...Money) => Money

    Example:

    add($('0.1'), $('0.2')).toString() === '0.30'; // true
    

    multiply()

    Take any number of money objects and return the product.

    multiply(...Money) => Money

    Example:

    multiply($(2), $(4)).toString() === '8.00'; // true

    Divide

    Take a dividend and divisor and return the quotient.

    divide(dividend: Money, divisor: Money) => Money

    Example:

    divide($(8), $(2)).toString() === '4.00'; // true

    Less Than

    Take a base and a comparand and return whether the comparand is less than the base.

    lt(base: Money, comparand: Money) => boolean

    Example:

    lt($(7), $(7.009)) === true; // true
    lt($(7), $(7)) === false; // false
    lt($(7), $(6.991)) === false; // false

    Greater Than

    Take a base and a comparand and return whether the comparand is greater than the base.

    gt(base: Money, comparand: Money) => boolean

    Example:

    gt($(7), $(7.009)) === false; // false
    gt($(7), $(7)) === false; // false
    gt($(7), $(6.991)) === true; // true

    Less Than or Equal to

    Take a base and a comparand and return whether the comparand is less than or equal the base.

    lte(base: Money, comparand: Money) => boolean

    Example:

    lte($(7), $(7.009)) === true; // true
    lte($(7), $(7)) === true; // true
    lte($(7), $(6.991)) === false; // false

    Greater Than or Equal to

    Take a base and a comparand and return whether the comparand is greater than or equal the base.

    gte(base: Money, comparand: Money) => boolean

    Example:

    gte($(7), $(7.009)) === false; // false
    gte($(7), $(7)) === true; // true
    gte($(7), $(6.991)) === true; // true

    $$ Ledger

    Takes any number of money objects (or functions of type Money => Money) and returns a money object containing the sum.

    $$(...Money) => Money

    Example:

    import { $ } from 'moneysafe';
    import { $$ } from 'moneysafe/ledger';
     
    $$(
      $(40),
      $(60),
      $(-5)
    ).toNumber(); // 95

    addPercent()

    Takes a percent x as a number and the current value in cents (curried), and returns a new money object representing the sum of the current value and x% of the current value.

    addPercent(percent: n) => (amount: Money) => Money

    Example:

    import { $ } from 'moneysafe';
    import { $$, addPercent } from 'moneysafe/ledger';
     
    const total = $$(
      $(40),
      $(60),
      addPercent(10)
    );
     
    console.log(
      total.toNumber() // 110
    );

    subtractPercent()

    Takes a percent x as a number and the current value in cents (curried), and returns a new money object representing the difference between the current value and x% of the current value.

    subtractPercent(percent: n) => (Money) => Money

    Example:

    import { $ } from 'moneysafe';
    import { $$, subtractPercent } from 'moneysafe/ledger';
     
    const total = $$(
      $(40),
      $(60),
      subtractPercent(10)
    );
     
    console.log(
      total.toNumber() // 90
    );

    Install

    npm i moneysafe

    DownloadsWeekly Downloads

    383

    Version

    2.2.1

    License

    MIT

    Unpacked Size

    22.5 kB

    Total Files

    13

    Last publish

    Collaborators

    • avatar