Muqsith
Muqsith

Reputation: 163

In JavaScript why is this behavior with large numbers

Below are three functions each one does the same thing but bit different way. They all multiply the numbers in array and return the result. But there is a strange behavior can someone please explain what is the difference.

function _mreduce0(name) {
  return name.split('').reduce((acc, e) => {
    return acc * 31 * e.charCodeAt(0);
  }, 7);
}

function _mreduce1(name) {
  let nArr = name.split('');
  let acc = 7;
  for (let i = 0; i < nArr.length; i += 1) {
    acc *= 31 * nArr[i].charCodeAt(0);
  }
  return acc;
}

function _mreduce2(name) {
  let nArr = name.split('');
  let acc = 7;
  for (let i = 0; i < nArr.length; i += 1) {
    acc = acc * 31 * nArr[i].charCodeAt(0);
  }
  return acc;
}

let n = 'somename';

console.log(_mreduce0(n) === _mreduce1(n)); // true
console.log(_mreduce0(n) === _mreduce2(n)); // true


// It is good for small size but when the string is large there is difference.
n = 'e868831414410da6b0b416be7e80f5211765ad4d1aed295cd24fcc17e72c03fc';

console.log(_mreduce0(n) === _mreduce1(n)); // false
console.log(_mreduce0(n) === _mreduce2(n)); // true

Upvotes: 2

Views: 132

Answers (3)

Peter B
Peter B

Reputation: 24136

Normally multiplication is associative, but it looks like the javascript engine in one case does a = a*b; a = a*c; and in the other case it does tmp = (b*c); a = a*tmp;. In both cases arithmetic overflow will occur, but in a different way so they behave differently.

Below is a sequence that shows the same effect. Differences occur, which then even resolve themselves, re-occur, etc.

var x = 7;
var y = 7;

console.log(x === y);
for (var i = 1; i < 50; i++) {
  x *= 31 * 3;
  y = y * 31 * 3;
  console.log('i = ' + i + ' -> ' + (x === y ? "equal" : "diff: " + (x - y)));
}

If we change it to use y = y * 93 then there is never a difference:

var x = 7;
var y = 7;

console.log(x === y);
for (var i = 1; i < 50; i++) {
  x *= 31 * 3;
  y = y * 93;
  console.log('i = ' + i + ' -> ' + (x === y ? "equal" : "diff: " + (x - y)));
}

My conclusion is that y = y * 31 * 3 calculates the righthandside from left to right, with possible arithmetic error effects after each *.

y *= 31 * 3 actually does the same, but here the righthandside is always 31 * 3 === 93 which it then multiplies with y.

Upvotes: 1

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48590

What about comparing 1 and 2? That is also false, so it looks like 0 and 2 are doing similar things. The acc *= expression seems to be the culprit here.

See: Why are shortcuts like x += y considered good practice?

You begin to overflow at iteration eight, because the values do not match.

7.43088268730786e+26 !== 7.430882687307858e+26

The difference between these two numbers is 137,438,953,472.

Full Demo

var valTable, n = 'e868831414410da6b0b416be7e80f5211765ad4d1aed295cd24fcc17e72c03fc';

valTable = [];
console.log('Are they the same?:', _mreduce0(n) === _mreduce2(n)); // true
console.log(displayValues(valTable));

valTable = [];
console.log('Are they the same?:', _mreduce0(n) === _mreduce1(n)); // false
console.log(displayValues(valTable));

function _mreduce0(name) {
  return name.split('').reduce((acc, e, i) => {
    var val = acc * 31 * e.charCodeAt(0);
    addToTable(valTable, i, 0, val); // Control
    return val;
  }, 7);
}

function _mreduce1(name) {
  let nArr = name.split(''), acc = 7;
  for (let i = 0; i < nArr.length; i += 1) {
    acc *= 31 * nArr[i].charCodeAt(0);
    addToTable(valTable, i, 1, acc); // Loop 1
  }
  return acc;
}

function _mreduce2(name) {
  let nArr = name.split(''), acc = 7;
  for (let i = 0; i < nArr.length; i += 1) {
    acc = acc * 31 * nArr[i].charCodeAt(0);
    addToTable(valTable, i, 1, acc); // Loop 2
  }
  return acc;
}

function addToTable(table, row, col, val) {
  if (table.length < row + 1) table.push([val]); else table[row][col] = val;
}

function displayValues(table) {
  return table.map((v, i) => [i, v[0] === v[1] ? 'yes' : 'no', v[0], v[1]].join('\t')).join('\n');
}
.as-console-wrapper { top:0; max-height:100% !important;}

Upvotes: 1

libik
libik

Reputation: 23029

Javascript have only one type - number - and number is always floating point type.

What it does mean? That it can actually skip some number or do not have exact result with counting with too big, too low or with decimal numbers.

What is "too big"? There is this value: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER

If you exceed this value (and you do), you cannot use native javascript number (well you can, but you can get results that are not precise)

Upvotes: 1

Related Questions