Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@dintero/money
Advanced tools
Quick examples
Money.of(2090.5, "EUR").toCurrency("NOK", 8.61).toString(); // 17999.21
Money.of(0.1, "NOK").add(Money.of(0.2, "NOK")).toString(); // 0.30
Money.fromPriceAndQuantity(0.0005, 30, "NOK").toString(); // 0.02
Money.fromPrice(0.001, "NOK").multiply(60).resetDecimals().toString(); // 0.06
Money.of(10, "NOK").distributeBy([1, 1, 1]); // list of moneys: [3.34, 3.33, 3.33]
Money.of(5.5, "NOK").toLocaleString("no-NB"); // 5,50
Money.fromLocaleString("5,50", "NOK", "no-NB").toString(); // 5.50
Money.of(12.5, "NOK")
.getVat(25, true) // Amount includes VAT
.toString(); // 2.50
Money.of(10, "NOK")
.getVat(25, false) // Amount does not include VAT
.toString(); // 2.50
Money.of("1234567891234567.25", "NOK").toNumber(); // Throws because the number cannot be represented as a double
See full api at the bottom of this page.
Javascript uses double floating point numbers.
There are several problems with these when it comes to handling money:
It's easiest to show by example:
// Without proper rounding, you'll accumulate small errors:
0.1 + 0.2; // => 0.30000000000000004
// With proper rounding, you'll simply get wrong results:
2090.5 * 8.61; // => 17999.204999999998
(2090.5 * 8.61).toFixed(2); // => 17999.20 // should have been 17999.21
// Even if the console.log manages to convert to base 10 for you,
// the number might not be what you think it is inside the double,
// and toFixed doesn't actually convert to base 10 properly.
// (see also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed)
(8.165).toFixed(2); // => 8.16 // should have been 8.17
// Math.round rounds towards 0, which doesn't make sense with money:
Math.round(-1.5) - // => -1 // Should have been -2 for money
// toFixed does it the correct way though:
(1.5).toFixed(0); // => -2 // Say what now
// 16 decimal digits are too much for javascript doubles
// Note how it doesn't really matter whether we're using whole numbers or not
Number(9999999999999999).toString(); // => 10000000000000000 // Too big for most money amounts, so probably fine
Number(999999999999999.9).toString(); // => 999999999999999.9
Number(99999999999999.99).toString(); // => 99999999999999.98
Number(9999999999999.999).toString(); // => 9999999999999.998
Number(999999999999.9999).toString(); // => 999999999999.9999
Number(99999999999.99999).toString(); // => 99999999999.99998
Number(9999999999.999999).toString(); // => 9999999999.999998
Number(999999999.9999999).toString(); // => 999999999.9999999
Number(99999999.99999999).toString(); // => 99999999.99999999
Number(9999999.999999999).toString(); // => 9999999.999999998 // If this was a price we would be in trouble
This library uses the excellent big.js library which does arbitrary decimal (base 10) arithmetic. This automatically takes care of all the issues above.
It is often said that one should use a whole number of the minor unit of the currency, e.g. a whole number of cents. This is (probably, see below) completely fine for transport and storage, but it is not fine for precision calculations.
Here's why:
In this library we've chosen to use big.js to avoid all potential pitfalls with whole number calculations.
Consider that a double has 53 bits for the significand. This is the part that has exact precision. 53 binary digits amounts to 15.95 decimal digits, or 15 to be safe.
So we have 15 digits to work with. How much can we fit in those?
The worlds GDP is about 8*10^13 USD, which requires 14 digits. In cents it would be 16 digits. So in USD we're just about able to store the entire world's GDP in cents without loss of precision.
Note that the exponent in the double doesn't change number of precise digits we get, so it doesn't matter if we store it as a whole number or a fractional number.
In any case, it's quite likely that javascript numbers are good enough for transport and storage for most use cases, even as fractional numbers. However, there are some potential pitfalls:
You'll have to know how large and precise your numbers will be before you choose how to store and transport them.
This library gives you a couple of options:
As we've seen, the first two have potential problems (however unlikely), but to mitigate the risk we make sure to throw an exception if you ever encounter these problems.
If you want to be completely safe, use strings.
Money is in a currency, and it usually has a major (e.g. Dollars) and minor unit (e.g. Cents).
This library ensures that one operates on correct currencies and the correct precision for the given currency. For intermediary calculations and price it is possible to adjust the precision.
big.js is quite fast and lightweight, but nowhere near the performance of native numbers.
The following code takes around 0.06ms, and just the addition itself is around 0.02ms:
Money.of(Math.random(), "NOK").add(Money.of(Math.random(), "NOK")).toNumber();
So you can do about 260 of those within a 60 fps frame, or around 800 pure additions. If we're talking larger jobs, you can do 300 million of the above in 5 minutes.
export type AdditionalOptions = {
decimals?: number; // Override precision
roundingMode?: RoundingMode; // See big.js rounding modes
tags?: Partial<Tags>; // Tag your money to keep track of what it represents
};
export declare class Money {
constructor(data: MoneyInputData);
/**
* Create a money object.
*
* Amount can be any of number, string, or Big
* currency is the 3-character currency code (ISO 4217)
* currency can also be set to UNKNOWN, which gives a precision of 2 decimals.
*
* With options you can specify
* - decimals
* - roundingMode
* - tags
*/
static of(
amount: NumberInput,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Instantiate from a string formatted in a certain locale.
*
* Examples:
* no-NB: 11 111,11
* en-GB: 11,111.11
* de-DE: 11.111,11
*
* Before parsing, non-numeric characters are removed, and the decimal sign is normalized.
*
* Locales with unicode numbers are NOT SUPPORTED
* Example of formats NOT SUPPORTED:
* ar: ١١٬١١١٫١١
*/
static fromLocaleString(
str: string,
currency: string,
locale?: string,
options?: AdditionalOptions,
): Money;
/**
* Instantiate from a whole number of minor units of the currency (e.g. whole number of cents)
*
* Example:
* Money.fromFractionlessAmount(1000, 'NOK') => 10.00 NOK
*/
static fromFractionlessAmount(
amount: number,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* A price has arbitrary precision.
* This method creates a Money instance with 6 decimals of precision by default.
* Remember to call .resetDecimals() when you want to go back to a proper Money value.
*/
static fromPrice(
price: NumberInput,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Calculate total money according to a price and quantity.
* Default precision is 6 decimals.
*/
static fromPriceAndQuantity(
price: NumberInput,
quantity: Factor,
currency: string,
options?: AdditionalOptions,
): Money;
/**
* Sum an array of moneys.
*
* If the array is empty, a currency must be specified so that 0 can be returned in that currency.
*
* The precision, rounding mode, etc, is based on the first item in the array.
* If the array is empty, the options object will be used instead.
*/
static sum(
moneys: Money[],
currency?: string,
options?: AdditionalOptions,
): Money;
static max(moneys: Money[]): Money;
static min(moneys: Money[]): Money;
/**
* Compare two money objects.
*
* 1 if money1 is greater than money2
* 0 if equal
* -1 if money1 is less than money2
*
* This can be plugged directly into array.sort(),
* and it will cause the array to be sorted in ascending order.
*/
static compare(money1: Money, money2: Money): number;
merge: (data: Partial<MoneyInputData>) => Money;
// Tags
/**
* Tags allow you to communicate more about what the money represents.
* You can later assert on a tag to make sure you're using the right amount for the right purpose.
*/
getTags: () => Tags;
getTag: <Name extends keyof Tags, Value>(
tagName: Name,
defaultValue?: Value | undefined,
) => Value | undefined;
setTag: <Name extends keyof Tags>(tagName: Name, value: any) => Money;
// Assertions
assertTag: <Name extends keyof Tags>(
tagName: Name,
value: any,
cmp?: (actual: any, value: any) => boolean,
) => Money;
assertSameCurrency: (money: Money) => Money;
// Utils
amount: () => Big;
currency: () => string;
// Converters
/**
* Converts the money amount into a whole number given in the minor unit of the currency
*/
toFractionlessAmount: () => number;
/**
* Converts to a regular javascript number.
* Throws an error if it's not possible to do keep full precision.
*/
toNumber: () => number;
toString: () => string;
toLocaleString: (locale?: string | undefined) => string;
toJSON: () => number;
/**
* Gets the current precision in use.
*/
getDecimals: () => number;
/**
* Override the default precision of the currency.
* Useful if you're working with a price.
*/
setDecimals: (decimals: number) => Money;
/**
* Reset precision back to that of the currency.
* Useful for converting from a price to the final monetary amount.
*/
resetDecimals: () => Money;
/**
* Converts to a different currency using a currency rate.
* Sometimes (rarely) the rate is given multiplied by a certain unit amount which has to be divided away.
*/
toCurrency: (
currency: string,
currencyRate?: Factor,
currencyUnit?: Factor,
) => Money;
//Math
round: (decimals: number, roundingMode?: RoundingMode | undefined) => Money;
multiply: (factor: Factor) => Money;
/**
* Note that dividing a monetary amount cannot be exact in all cases.
* E.g. 10 NOK / 3 = 3.33 NOK
* Use `distribute` or `distributeBy` if you need an exact distribution.
*
* The division is performed with a precision of 20 decimals before
* rounding back to the monetary amount. (See https://mikemcl.github.io/big.js/#dp)
*/
divide: (divisor: Factor) => Money;
add: (money: Money) => Money;
subtract: (money: Money) => Money;
abs: () => Money;
// Comparisons
equals: (money: Money) => boolean;
greaterThan: (money: Money) => boolean;
greaterThanOrEqual: (money: Money) => boolean;
lessThan: (money: Money) => boolean;
lessThanOrEqual: (money: Money) => boolean;
isZero: () => boolean;
/**
* Positive and not 0
*/
isPositive: () => boolean;
/**
* Negative and not 0
*/
isNegative: () => boolean;
// Distribute
/**
* Divides money into n parts.
*
* Example:
* Money.of(10, 'NOK').distribute(3) => [3.34, 3.33, 3.33]
*
* Distributes any rest amount equally across the parts
*/
distribute: (nParts: number) => Money[];
/**
* Divides money into parts, each defined by a weight.
*
* Each weight must be >= 0
* Total of weights must be > 0
*
* Example:
* Money.of(10, 'NOK').distributeBy([1, 1, 1]) => [3.34, 3.33, 3.33]
*
* Distributes any rest amount equally across the parts
*/
distributeBy: (inputWeights: Factor[]) => Money[];
// VAT
addVat: (vatPercentage: Factor) => Money;
removeVat: (vatPercentage: Factor) => Money;
getVat: (vatPercentage: Factor, includesVat?: boolean | undefined) => Money;
}
FAQs
Quick examples
The npm package @dintero/money receives a total of 584 weekly downloads. As such, @dintero/money popularity was classified as not popular.
We found that @dintero/money demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.