Why BigInt is a big deal
Why don't these numbers add up?
In 2019, I was working for a financial company and we displayed financial information (revenues, cost, forecasts ...) of clients to potential investors. Most of that information came from small businesses, but apparently not small enough
I remember getting a call from one of my colleagues: "Hi Karel, I'm getting some info from a client that the numbers he has uploaded to our site don't seem to be displayed correctly. Could you have a look?"
So I dove in and indeed, the numbers that were uploaded by the client didn't add up. There were numbers for several years that were uploaded, and the numbers for those latest years didn't match the data that was uploaded. We reproduced the problem on our test environments and figured there was a problem when column sizes exceeded a specific number.
But numerous tests later, we realized the problem lay with the numbers itself. They seemed to be cut off to 21.474. The developer knew it now: the problem was in the data type. And sure, we found that the max value for INT in MySQL is 2147483647.
Since we always included 2 decimal points, the maximum value we could store was 21.474.836,47. And when divided by 1.000 and displaying it without decimal points, that came down to 21.474. Mystery solved!
And just like in Java, you can easily access that maximum or minimum value:
Number.MAX_SAFE_INTEGER // 9007199254740991 Number.MIN_SAFE_INTEGER // -9007199254740991
Now, it seems difficult to spot problems with a value this high. But I was forced to think again.
Arithmetic for bank accounts
When building my IBAN generator/validator, I wrote some tests in Jest and after running them a couple of times, I decided things were good to go. But when I returned to the application, strange things were happening.
3214282912345698765432161182 > Number.MAX_SAFE_INTEGER // true
The question is now: how can we safely proceed? Well, you can coerce a Number to a BigInt by adding "n" to the end of a number or by calling the BigInt constructor with a string that contains the integer value. Which leads to the following operations for our example number.
3214282912345698765432161182n % 97n // 1n BigInt("3214282912345698765432161182") % BigInt(97) // 1n
Notice how you also can't perform these mathematical operations if both numbers aren't coerced to the same type (BigInt or Number).
BigInt("3214282912345698765432161182") % 97 // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
The main problem ofcourse is that you don't see the issues coming for (too) large numbers. Because when you perform this modulus operation without coercing to BigInt, you'll get a result that doesn't spell disaster.
Instead, it puts us on the wrong foot:
3214282912345698765432161182 % 97 // 65
It took me a while to figure it out so I got burned twice by the same flame. But that doesn't matter, I learned from it and next time, I'll see it coming even faster.