0

How can I determine if a number is too big (will cause rounding errors if math is performed) in JavaScript.

For example, I have a function that formats percentages. If it cannot format the passed in value correctly, I want it to return the value exactly as it was passed in.

function formatPercent(x, decimals) {
     var n = parseFloat(x);                 // Parse (string or number)
     if ($.isNumeric(n) === false) {
         return x;                          // Return original if not a number
     } else {
         return n.toFixed(decimals) + '%';  // Return formatted string
     }
 };

alert(formatPercent(276403573577891842, 2)); // returns 276403573577891840.00%

Since formatting such a huge number is a corner case and not expected, I'd prefer to just return the number as it was passed in. What is the limit before the rounding errors start and how would I check for them?

Update:

What is JavaScript's highest integer value that a Number can go to without losing precision? says precision works up to +/- 9007199254740992. I am testing to see if that is all I need to check against to safely fail and return the passed in value unmodified.

Community
  • 1
  • 1
Justin
  • 26,443
  • 16
  • 111
  • 128
  • This depends on the source of the numbers, really, and what you intend to do with them. I don't honestly see an application involving percentages that large, so what is this really for? – Patrick Roberts Jul 19 '13 at 19:53
  • I am writing unit tests (QUnit) and want to make sure my results are reliable. I have similar functions that format numbers (with commas, decimals) for things like impressions, and clicks that would be bigger numbers ... still not expecting a 64-bit int like the one above, but in writing my unit tests, it would be nice to catch it anyway. – Justin Jul 19 '13 at 20:46
  • The article you linked does make sense (9007199254740992).toString(16) == "20000000000000" So I guess just adding an if statement saying if it's above or equal to that value will determine whether or not to trust that's its real value. – Patrick Roberts Jul 19 '13 at 21:18

4 Answers4

1

If you always pass x in as a string, that will ensure that there are no rounding errors. The problem is that 276403573577891842 is being rounded right as the number literal is parsed, but if you use strings, that will never happen. Try doing this:

function formatPercent(x, decimals) {
    if(typeof x != "string" && typeof x != "number") return x;
    x = x+"";//convert to string if it is a number
    var r = /^(?:(\d+)(\.\d*)?|(\d*)(\.\d+))$/;// RegExp for matching numerical strings
    return x.replace(r, function(match, int, dec){
        if(decimals>0){
            int = (typeof int == "string"?int:"");//if passed string didn't have integers
            dec = (typeof dec == "string"?dec:".");//if passed string didn't have decimals
            while(dec.length-1<decimals) dec += "0";//pad zeroes until dec.length-1==decimals
            return int+dec.slice(0,decimals+1)+"%";//in case dec.length-1>decimals
        }
        int = (typeof int == "string"?int:"0");//if passed string didn't have integers
        return int+"%";
    });
    // Return formatted string or original string conversion if no match found
}

alert(formatPercent("276403573577891842", 1));// returns 276403573577891842.0%
alert(formatPercent("276403573577891842.55", 1));// returns 276403573577891842.5%
alert(formatPercent("276403573577891842.55", 0));// returns 276403573577891842%
alert(formatPercent(".55", 1));//returns .5%
alert(formatPercent(".55", 0));//returns 0%
alert(formatPercent(276403573577891842, 1));// returns 276403573577891840.0%
alert(formatPercent("this is not a number", 2));// returns this is not a number
alert(formatPercent({key:"not number or string"}, 2));// returns the object as it was

Even though formatPercent still fails in the case of passing a number, this will prevent rounding error from passed strings. Please note this is not incorrect, as the only case in which it will fail is when a number that is too large is hard-coded as a parameter.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • Thanks, +1 as this does solve the math problem. However, I don't think I'd want to use it in production as it's fairly complex and as you mentioned in your comment, handling numbers this great isn't needed. However, knowing when a value passed in is too big, is needed. I will update my question. – Justin Jul 19 '13 at 21:11
0
function formatPercent(x, decimals) {
     var n = parseFloat(x);                 // Parse (string or number)
     if ($.isNumeric(n) === false) {
         return x;                          // Return original if not a number
     } else {
         var d = Math.pow(10, decimals)
         return (Math.round(n * d) / d).toString() + "%";
      }
 };

Using rounding will drop the decimal if it's .00

kcathode
  • 94
  • 3
  • 1
    This isn't correct, since running the code produces "276403573577891840%" rather than "276403573577891842%" – Patrick Roberts Jul 19 '13 at 20:01
  • 1
    Ah true, max int is 2^53 per below. Works for smaller floats however. http://stackoverflow.com/questions/307179/what-is-javascripts-max-int-whats-the-highest-integer-value-a-number-can-go-t – kcathode Jul 19 '13 at 20:12
0

This is what I used to catch too big or too small integers:

function getSafeNumber(x) {
     var n = parseFloat(x);    // Parse and return a floating point number
     if ($.isNumeric(n) === false || 
         n >= 9007199254740992 || 
         n <= -9007199254740992) {
         return false;         // Not numeric or too big or too small
     } else {
         return n;             // return as number
     }
 };
Community
  • 1
  • 1
Justin
  • 26,443
  • 16
  • 111
  • 128
-1

No greater than nine quardbillion will cause this type of error due to internal representation of no