Dinero.js – JavaScript library for working with monetary values
sarahdayan.github.io
sarahdayan.github.io
let added = Dinero({ amount: 0.1 }).add(Dinero({ amount: 0.2 }))
let result = added.equalsTo(Dinero({ amount: 0.3 }))
`result` is false! I can't believe there's a currency library that doesn't do correct decimal arithmetic.The best are acceptance tests. But here the use of SaaS systems with no sandbox or locally deployable mocks mean this is hard to automate.
Perhaps it should not allow you to create an instance with a non-integral `amount` ?
It is a serious problem that `Dinero({amount: 0.1})` doesn't immediately throw an exception, though—if you design a library that horribly breaks on non-integer inputs, you must reject integer inputs loudly and immediately.
Prices are a different issue, you're likely to have prices that are fractions of a penny; but any inventory valuations (either as stock or sale) would again be rounded after multiplying the price with the item quantity.
Perhaps this could use some integration with a general concept of unit dimensions, distinguishing values that are measured in dollars(cents) and values such as prices that are "dollars/item" and can become "dollars" only when multiplied by number of items.
I get the impression back then they everybody in the gaming/gambling industry did this all the time - to the extent that they knew how to organise source code and name variables and functions so the regulators auditing the code would just rubber stamp it.
(I was not "in the gaming industry", we were just doing a mobile friendly front end, in jQuery Mobile, to run on the hot new Samsung Galaxy S2... Now I think I need to go curl up in a dark corner and cry myself to sleep again... Project. From. Hell...)
Would it be possible to rewrite this library to use "floatish" numbers with initial multiplication and flooring then using decimal.js? I understand that non-internal arithmetics are slow as hell, but most of the time people just add products to a cart or sometimes apply a percentage discount.
On the other hand, please do throw for non-integer values :)
Plug in 0.11 and 0.1 and click `+`. You expect 0.21, but since 1/10 isn't cleanly represented in base 2 using a finite number of bits... (just like 1/3 isn't cleanly represented in base 10 ~0.33..).
And when it comes to currency, rounding and "good enough" is probably not going to be good enough.
Probably I'd like to do the last step with a custom arithmetic and not store the final value as a float, but like I said, I'm not sure. I get that the lowest denominator route is good, but it feels complicated and the chance of migrating a shoddy "good enough" project is zero in that case.
(For fuck's sake, in 2018 formatting text or using utf8 on hacker news is too much to ask for, it's even worse than slashdot)
It might be fine, depending on the use case. I think dealing with any complicated currency arithmetic and throwing floating point arithmetic on top is throwing gas on a fire.
That said, I consider this to be similar to the phone number representation. As in, what is the correct way to store and manipulate a phone number? 1 555 555 5555 looks a lot like a number, but does it make sense to increment by 1 or divide by 5?
Similarly, $1.12 looks a lot like a float, but would you ever have $pi? In practice currency acts more like an integer with possibly infinite digits i.e. probably a candidate for something like Java's BigInteger. But that's just my $0.02 :)
In cases where that isn't possible, try an infinite precision javascript library. https://github.com/MikeMcl/big.js
You could _probably_ find a way to shim big.js into OP's library :)
In British Pounds, it would be pennies for example.
The doc doesn't explain how the library deals with the various different minor units out there (I've been bitten too many times by code that assumes that all currencies have a minor unit and that 100 minor units = 1 major unit so I'm careful these days).
For example, what about currencies that have a minor unit that's not effectively used in practice or that don't have a minor unit at all (e.g. JPY)? Are the amount supposed to be expressed in this unused / non-existent minor unit?
What happens with currencies that use a subdivision other than 100 for their minor unit (e.g. KWD)? Will the calculations and formatting be correct?
I'm missing something here? Do you mean you're storing mills (₥)?
In JavaScript every number is a IEEE754 floating point number, and removing decimal places will not change its representation in memory or its limitations.
The only situation when a JavaScript Number type is treated as an integer is a vendor specific optimization for small integers (e.g: Smi in v8).
You can still run into issues such as absorption problems if dealing with only "integers". e.g:
> 1e16 -1
10000000000000000 // wrong
> 1e16 -2
9999999999999998
> 1e16 -3
9999999999999996 // wrong
> 1e16 -4
9999999999999996
> 1e16 -5
9999999999999996 // wrong
> 1e16 -6
9999999999999994
> 1e16 -7
9999999999999992 // wrong
But this is just the beginning.This is not true: with 64-bit floating-point numbers, calculations on numbers in a range from Number.MIN_SAFE_INTEGER (2^53-1) to Number.MAX_SAFE_INTEGER (-2^53-1) are safe to do. If you go outside this range, the numbers are no longer exact, so you'll have to check for overflow, but it's the same when you use native machine integer types (e.g. uint64) in most languages that have them.
The only situation when a JavaScript Number type is treated as an integer is a vendor specific optimization for small integers (e.g: Smi in v8).
A number is integer if it doesn't have a fractional component regardless of how it's represented by a computer.
There are also operations in JavaScript that treat their inputs as 32-bit integers (numbers are taken modulo 2^32), such as bitwise operations and Math.imul. But if you don't use them, integers can be as large or as small as described above.
> 1.03 - 0.42
0.6100000000000001But what happens after successive operations? the risk increases. Is this a good idea when dealing with currency? No. Does this library warn you about those cases or throw an error? no. Does this library provide unit tests for those cases? no.
Functions such as allocate() are super useful:
// returns an array of two Dinero objects
// the first one with an amount of 502
// the second one with an amount of 501
Dinero({ amount: 1003 }).allocate([50, 50])I wonder if it might be even better for the ExchangeRate constructor to take two different Dense values, something like:
exchangeRate (1 :: Dense "EUR") (12 % 10 Dense "USD")
Then the type system could automatically prevent you from making an error by accidentally entering the reciprocal exchange rate from what is expected.
P.S. As much as I hate DEC64, it is a much better way to handle money. Someone please add DEC64 to JavaScript! We have Set and generator functions. Why not another way to store floating point numbers!
I'm sure you're aware, but it should be mentioned that all languages using single-precision FP by default behave the same. Ruby, Python, Perl, PHP (or any other if you use float instead of double).
nr = nr | 0
Then use the cents instead of decimal dollars.But it might be a good idea to use a bigint lib. And remember different countries might have different rounding rules.
2147483648 | 0 = -2147483648
-2147483649 | 0 = 2147483647
4294967296 | 0 = 0
-4294967296 | 0 = 0
6442450944 | 0 = -2147483648 2**53-1 | 0 = -1
2**53-2 | 0 = -2
Which is modular arithmetic: (2**53-2) % 2**32 = 4294967294
2**53-2 >>> 0 = 4294967294
( >>> 0 makes unsigned, 4294967294 | 0 = -2)Don't be "clever". Use parseInt or parseFloat.
parseInt(0.000001) // 0
parseInt(0.0000001) // 1parseInt() only accepts strings as input. So JavaScript calls .toString() on the number before passing it as an argument.
(0.000001).toString() // "0.000001"
(0.0000001).toString() // "1e-7"
By the way, TypeScript or Flow would have caught this error during compile time.Math.round() Math.ceil() Math.floor()
npm pages for packages all have a "test in your browser" link that goes to RunKit.
Looks like it does lose cents and can't be used in production.
from the source:
divide(a, b) {
return a / b
},The Number type in JavaScript is a IEEE754 floating point number. Someone explained why floating point numbers are not good to represent currency:
https://stackoverflow.com/questions/3730019/why-not-use-doub...
In fact, even a simple operation like equality in floating point numbers becomes harder. e.g:
Math.abs(x - y) < Number.EPSILON
I would be very careful about this since it becomes problematic for large numbers.It’s terrible to not even have proper integers but that doesn’t make the problem go away. People still have to do financial calculations in JS.
You can not just take two “Number” and use for proper decimal math without enforcing some invariants.
No, they don't.
Source: Gordon Zhu’s excellent introduction to JS course, Watch and Code [1]. I haven’t used accounting.js myself.
Converting an amount to cents, e.g: $1.99 becomes 199 cents, will help you only briefly.
As numbers become larger or you have successive divisions, numbers will start to suffer from issues such as absorption. Then you may also suffer from issues when comparing numbers, since floating point equality is different.
e.g:
> 1e16 -1
10000000000000000
Note that this code should output 9999999999999999. But it doesn't because of absorption. In this case, "multiplying by a few thousands" makes this situation more likely.For whoever says "you don't manipulate money in js", there's nodejs and many devs actually have to manage them.
I've used the following library in the past:
* https://mikemcl.github.io/bignumber.js/
It's also worth noting that the maximum integer value that can be safely stored by the Number type in JavaScript is 2^53 (9007199254740992). So if you expect to be working with integers larger than this, you should use a library for working with large integers (private keys in cryptocurrencies). The one linked above works, but there are others as well.