The amounts must be specified in cents, as floats can’t be relied upon to perform calculations. That was my #1 reason to design the library using solely integers. I wrote all about it here: https://frontstuff.io/how-to-handle-monetary-values-in-javas...
By "cents", you really mean "minor currency unit" I assume?
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?
But there's no way to do so in JavaScript unless you implement a numeric type by hand, something that Dinero does not do.
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:
But there's no way to do so in JavaScript unless you implement a numeric type by hand
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.
You claim that as long as numbers fit in a double precision IEEE754 floating point number significand, which is what JS implements, you are safe. Fine.
But 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.
I agree that this library doesn't looks good — a proper implementation would use a better precision (e.g. 1/10000 of a currency unit) rather than "cents" (1/100), and of course, check ranges and the absence of fractions (Number.isSafeInteger).
You may have designed it using only integers, but it's very easy to use it with non-integers, which will result in hard-to-diagnose errors. It should probably throw if a non-integer is encountered. Failing that, a huge blinking red banner at the top of the docs should explain the problem.
I've read the post and I do get the point BUT we currently support 25 languages, use accounting.js (I know, it's just formatting) and the backend returns float values and we still get by. I'm aware of the risks and not happy about it, but probably good will and magic holds it together (plus backend has a veto over the price with the final, canonical value on their side).
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.
It may be totally silly (I consider myself lucky for getting by with good enough so far:)), but would `(Math.floor(0.11x10xx2)+Math.floor(0.1x10xx2))` divided by 100 work?
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)
I don't know enough of IEEE 754, but I don't think binary representation of numbers are guaranteed to be strictly greater i.e. I think you at least want Math.round instead of Math.floor.
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 :)
Basically, you store everything as integers.You can specify whatever precision you want, just by saying x digits are before the decimal. E.g. 1.234566 is 1234566. Rounding when needed(just be consistent), but you never round until the last step which is when you're going to be displaying/storing the final value.