1

As described in MDN the following polyfill can be used for Math.log10:

Math.log10 = Math.log10 || function(x) {
  return Math.log(x) / Math.LN10;
};

Unfortunately, due to floating point arithmetic (in the non native implementation), this:

Math.log10(1000000)

will result to:

=> 5.999999999999999

One way to get around this issue is to do some rounding:

rounding_const = 1000000;

function log10(n) {
  return Math.round(rounding_const * Math.log(n) / Math.LN10) / rounding_const;
}

Is there a better approach (algorithmic?) which will not involve hacky rounding?

kongeor
  • 712
  • 1
  • 6
  • 14
  • Possible duplicate of [How to deal with floating point number precision in JavaScript?](http://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript) – JJJ Jun 04 '16 at 12:40
  • 1
    What will be the negative impact of the result of `5.99999999999`? In other words, how are you using this? –  Jun 04 '16 at 13:29
  • You're using floating point, you should expect a little inaccuracy and be able to deal with it. – Barmar Jun 04 '16 at 13:46

3 Answers3

0

I guess you can use your code to calculate an approximation, and then do a binary search until you reach the desired value. Something like this (can probably be improved)

function log(x) {
  var aprox = Math.log(x) / Math.LN10,
      value = Math.pow(10, aprox);
  if(value === x) return aprox;
  if(value < x) {
    var lower = aprox,
        lowerval = value,
        upper = aprox * 1.00000000001,
        upperval = Math.pow(10, upper);
  } else if(value > x) {
    var lower = aprox * .99999999999,
        lowerval = Math.pow(10, lower),
        upper = aprox,
        upperval = value;
  } else {
    return aprox;
  }
  var iterations = 1e3;
  for(var i=0; i<iterations; ++i) {
     var middle = (upper+lower) / 2,
         middleval = Math.pow(10, middle);
    if(middleval < x) {
      lower = middle;
      lowerval = middleval;
    } else if(middleval > x) {
      upper = middle,
      upperval = middleval;
    } else {
      return middle;
    }
  }
  return (upper+lower) / 2;
};
log(1000000); // 6
Oriol
  • 274,082
  • 63
  • 437
  • 513
0
Math.log10 = function (x) {
  if (!(x > 0 && x < 1 / 0)) {
    return Math.log(x) * Math.LOG10E;
  }
  var n = Math.round(Math.log(Math.min(Math.max(x, 10 / Number.MAX_VALUE), Number.MAX_VALUE / 10)) * Math.LOG10E);
  return n + Math.log(x / Math.pow(10, n)) * Math.LOG10E;
};
4esn0k
  • 9,789
  • 7
  • 33
  • 40
-1

you can do it this way, but I don't know wether this is any better than the rounding.

Math.log10 = Math.log10 || function(value){
  var v = +value, s;
  if(v > 0 && /^(?:10*|0\.0*1)$/.test(s = ""+v)){
    return v < 1? 
      2 - s.length:
      s.length - 1;
  }
  return Math.log(v) / Math.LN10
}

btw. your rounding_const can be way higher, sth. around 1e12 to 1e14

Thomas
  • 3,513
  • 1
  • 13
  • 10