8

I've got two numbers that I want to compare. The numbers in the following example are the result of 26^26 computed in two different systems. One of which is my javascript code.

However, when comparing the two numbers I end up with something like this:

AssertionError [ERR_ASSERTION]: 4.0329146112660565e+26 == 4.0329146112661e+26

They're obviously not equal, but theoretically they should.

What's the proper way to perform equality on big numbers in javascript (even if it's an approximation)?

President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
Waseem
  • 651
  • 1
  • 5
  • 15
  • 1
    https://github.com/MikeMcl/bignumber.js/ – Mojtaba Kamyabi Nov 04 '17 at 06:35
  • You can convert the numbers to strings then perform comparison – guest271314 Nov 04 '17 at 06:36
  • 1
    What do you mean by **equality**? Strictly equal? or equal within some digits? or equal within fixed precision? or numbers that can be rounded to each other? Please specify – Vasily Liaskovsky Nov 04 '17 at 06:43
  • I think the error is not because of two big numbers but for incorrect comparison. Can you please provide the code? – ShacharW Nov 04 '17 at 06:48
  • @VasilyLiaskovsky In this case, fixed precision is probably what I'm looking for. – Waseem Nov 04 '17 at 06:49
  • 3
    Remember: In javascript every `Number` is a 64 bit float, so the `Number.MAX_SAFE_INTEGER` constant represents the maximum safe integer in JavaScript (2**53 - 1). *Edit*: so you could only check 'equality' for two floating point numbers if you calculate and round them in the same way. When you had real int's you'd have the mantra: only x amount of LSB's are stored, with float you need the mantra of only the first 53 MSB's are stored (the rest is discarded). – GitaarLAB Nov 04 '17 at 06:51
  • There is useful answer here https://stackoverflow.com/questions/5037839/avoiding-problems-with-javascripts-weird-decimal-calculations – Vasily Liaskovsky Nov 04 '17 at 06:57
  • 1
    @GitaarLAB great answer! – Waseem Nov 04 '17 at 07:03

4 Answers4

4

Update: If your target engine is es2020 or above, you can use the new BigInt javascript primitive, for numbers higher than Number.MAX_SAFE_INTEGER

BigInt(4.0329146112660565e+26) === BigInt(4.0329146112661e+26)
//false

See more information in MDN

ben marder
  • 105
  • 1
  • 7
3

If what you're trying to do is determine if two numbers are practically equivalent you'll have to come up with your margin of error. One way to do this is to compute the difference between the numbers and then determine if that difference is significant or not.

So, taking your numbers from before, we could evaluate the difference between these numbers through subtraction. Since we don't really care about the sign of this difference, I'll go ahead and get the absolute value of the difference.

Math.abs(4.0329146112660565e+26 - 4.0329146112661e+26) === 4329327034368

(Sidenote: Now is not the time to explain why, but the == operator in JavaScript has confusing and error-prone behavior, use === when you want to compare values.)

That difference is a HUGE number, but related to how big our numbers are in the first place, it's rather insignificant. Intuitively, I'm tempted to divide the difference by the smallest of our original numbers like so:

4329327034368 / 4.0329146112660565e+26 === 1.0734983136696987e-14

That looks like a pretty small number. Repeat that same operation with a bunch of values and you should be able to determine what you want your margin of error to be. Then, all you'll have to do is perform the same operations with arbitrary numbers and see if that "difference ratio" is small enough for you.

function similar(a, b) {
  let diff = Math.abs(a - b);
  let smallest = Math.min(Math.abs(a), Math.abs(b));
  let ratio = diff / smallest;
  return ratio < MARGIN_OF_ERROR;
}

Now I just came up with that way of determining the importance of the difference between two numbers. It might not be a very smart way to compute it, it might be appropriate to some situations and not to others. But the general idea is that you'll have to make a function that determines if two values are close enough with your own definition of "close".

Be aware though, JavaScript is one of the worst languages you can be doing math in. Integers become imprecise when they go beyond Number.MAX_SAFE_INT (which seems to be 9007199254740991 according to Chrome, not sure if it varies between browsers or if that's a standardized constant).

Domino
  • 6,314
  • 1
  • 32
  • 58
  • 1
    standardized constant! Natural result of IEEE 754 float: remember the mantra, just the 53 FIRST bits are stored (and an exponent) Edit: the reason we don't include the last possible value (but subtract one) is becouse we want SAFE int, and we can not distinguish 2**53+1 from 2**53 – GitaarLAB Nov 04 '17 at 07:13
  • This actually goes for **relative** precision, not **fixed**, but yes, it definitely can help – Vasily Liaskovsky Nov 04 '17 at 07:13
  • 1
    sidenote: nothing wrong with javascript math (I do it a lot). Just understand there is just ONE numerical type: IEEE 747 64bit float. You still have my +1 though! Edit: you can even do reliable math with numbers larger than that, as long as the values you want to represent do not need more than 53 significant bits! Once one understands that you can implement pretty interesting algorithms (usually binary) using native numbers – GitaarLAB Nov 04 '17 at 07:20
1
var a = 4.0329146112660565e+26;

var b = 4.0329146112661e+26;

a = Math.round(a/10e+20)*10e+20

b = Math.round(b/10e+20)*10e+20

a == b;
AlexSp3
  • 2,201
  • 2
  • 7
  • 24
Daniel Lord
  • 754
  • 5
  • 18
  • @Daniel Lord Thanks! `10e+20` seems a bit of a magic number. do you mind explaining the logic behind it? This solution combined with @GitaarLAB's suggestion about the `Number.MAX_SAFE_INTEGER` does the trick for me. `Math.round(4.0329146112661e+26 / Number.MAX_SAFE_INTEGER) * Number.MAX_SAFE_INTEGER;` – Waseem Nov 04 '17 at 06:58
  • @Sam D. It seems that you want the precision to be less than 12, as that is where the float differs. This solution rounds to a precision of 26 - 20 + 1 = 7. At a precision of 7 logic on the float works. Change the 20 to gain or lose precision on the comparison. – Daniel Lord Nov 04 '17 at 07:14
  • why cast `20` and `26` to a Number, it's already a number: `10e+20 == 10e20` – dandavis Nov 04 '17 at 07:21
0

I would suggest to use one of big numbers library:

  1. big.js (https://www.npmjs.com/package/big.js)

Example:

var x = new Big('4.0329146112660565e+26');

var y = new Big('4.0329146112661e+26');

// Should print false
console.log('Comparision result' + x.eq(y));
  1. big-numbers (https://www.npmjs.com/package/big-numbers)

Example:

var x = bn.of('4.0329146112660565e+26');
var y = bn.of('4.0329146112661e+26');

// Should print false
console.log('Comparision result' + x.equals(y));
alexey28
  • 5,170
  • 1
  • 20
  • 25