npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

moneysafe

v2.2.4

Published

Convenient, safe money calculations in JS

Downloads

8,132

Readme

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 arithmetic 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 converting currency amounts into BigNumbers and delegating arithmetic operations to it, which guarantees precision to the number of decimals specified for the currency you are working with.

Two currencies are provided out-of-the-box, dollars (precise to 2 decimal places, exposed via the $ utility) and Ethereum (precise to 18 decimal places, exposed via the ethereum utility), but you can create your own via the createCurrency factory:

import { createCurrency } from 'moneysafe';

const bitcoin = createCurrency({ decimals: 8 });

$(dollars) => Money

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

$(dollars: n) => Money

Example:

$(20).toFixed(); // '20.00'

Once a value is represented as money, you can still operate on it using normal JavaScript operators - however, doing so will be subject to JavaScript Number coercion and its inherent rounding problems. You should use the arithmetic functions provided to guarantee precision instead:

// using JavaScript operators
$(0.1) + $(0.2); // 0.30000000000000004

// using Money arithmetic functions
$(0.1).plus(0.2).valueOf(); // 0.3

Migrating from version 1

In version 1, Money$afe would lift dollar amounts into cents, which would allow you to use normal JavaScript operators for arithmetic and then convert the value back into dollars (rounded to the nearest cent) using a in$ utility.

Since Money$afe no longer lifts amounts into cents (in order to support more currencies), the in$ utility has been removed.

Since version 2, we recommend using only the provided arithmetic functions (instead of normal JavaScript operators) when working with Money types, and there is no need to convert a lifted Money type back into a dollar amount.

If you want to round a Money value to its nearest supported significant digit, you can use money.toFixed(), combined with JavaScript Number coercion if necessary:

// v2 example
import { $ } from 'moneysafe';

const string = $(-45).div(99).toFixed(); // '-0.45'
const number = +string; // -0.45

For sake of comparison, here's an example of what this might have looked like using Money$afe v1:

// v1 example - this no longer works!
import { in$, $ } from 'moneysafe';

const number = in$($(-45) / 99); // -0.45

$ 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
);