Rounding Numbers in Javascript

Javascript is incapable of rounding numbers to a certain number of decimal places accurately. The problem occurs because Javascript treats decimal numbers as floats, which, as most programmers know, are a pain in the proverbial. As such, if you try to round a number like 0.285 using Javascript (using either Math.round or the toFixed function) you end up with 0.28 instead of 0.29. After a half-hearted search on google, I could not easily find a sample script that I could use in nBill to calculate this accurately (because nBill is commercial, and the only sample I found would not allow me to use it for commercial purposes). So I wrote my own function from scratch...

You can use this function wherever you like, in commercial or non-commercial applications, as long as you keep the copyright message and URL to my website intact in the source code. It is offered 'as is' without any warranties of any sort. There might be bugs in this code, and it might be possible to improve on its efficiency. If you have a suggestion on how to improve it, please contact me.

Try it out:
Number:   Decimal Places:    Result:  

First, a condensed version for faster page loading and reduced bandwidth usage:

function round_number(number,dec_places){
//Version 2.0 (c) Copyright 2008, Russell Walker, Netshine Software Limited. www.netshinesoftware.com
var new_number='';var i=0;var sign="";number=number.toString();number=number.replace(/^\s+|\s+$/g,'');if(number.charCodeAt(0)==45){sign='-';number=number.substr(1).replace(/^\s+|\s+$/g,'')}dec_places=dec_places*1;dec_point_pos=number.lastIndexOf(".");if(dec_point_pos==0){number="0"+number;dec_point_pos=1}if(dec_point_pos==-1||dec_point_pos==number.length-1){if(dec_places>0){new_number=number+".";for(i=0;i<dec_places;i++){new_number+="0"}if(new_number==0){sign=""}return sign+new_number}else{return sign+number}}var existing_places=(number.length-1)-dec_point_pos;if(existing_places==dec_places){return sign+number}if(existing_places<dec_places){new_number=number;for(i=existing_places;i<dec_places;i++){new_number+="0"}if(new_number==0){sign=""}return sign+new_number}var end_pos=(dec_point_pos*1)+dec_places;var round_up=false;if((number.charAt(end_pos+1)*1)>4){round_up=true}var digit_array=new Array();for(i=0;i<=end_pos;i++){digit_array[i]=number.charAt(i)}for(i=digit_array.length-1;i>=0;i--){if(digit_array[i]=="."){continue}if(round_up){digit_array[i]++;if(digit_array[i]<10){break}}else{break}}for(i=0;i<=end_pos;i++){if(digit_array[i]=="."||digit_array[i]<10||i==0){new_number+=digit_array[i]}else{new_number+="0"}}if(dec_places==0){new_number=new_number.replace(".","")}if(new_number==0){sign=""}return sign+new_number}

 ...and now the full commented version so you can see how it works:

function round_number(number, dec_places)

{

    //(c) Copyright 2008, Russell Walker, Netshine Software Limited. www.netshinesoftware.com   
    //Version 2.0. Change log:
    //18/12/08 Fixed bug where digits after decimal point greater than 995
    //29/01/09 Added support for negative numbers (symmetrical rounding) and strip white space
    //12/03/09 Fixed bug where first digit is a 9 and needs to be rounded up
    var new_number = '';
    var i = 0; //Just used in loops
    var sign = ""; //If negative, a minus sign will be prefixed to the result
    number = number.toString(); //We need to operate on and return a string, not a number
    number = number.replace(/^\s+|\s+$/g, ''); //Remove any excess white space
    
    //Do we have a negative number?
    if (number.charCodeAt(0) == 45) //minus sign
    {
        sign = '-';
        number = number.substr(1).replace(/^\s+|\s+$/g, '');
    }
    
    dec_places = dec_places * 1; //We need an integer
    dec_point_pos = number.lastIndexOf(".");
   
    //If there is nothing before the decimal point, prefix with a zero
    if (dec_point_pos == 0)
    {
        number = "0" + number;
        dec_point_pos = 1;
    }
   
    //Has an integer been passed in?
    if (dec_point_pos == -1 || dec_point_pos == number.length - 1)
    {
        if (dec_places > 0)
        {
            new_number = number + ".";
            for(i=0; i<dec_places; i++)
            {
                new_number += "0";
            }
            if (new_number == 0)
            {
                sign = "";
            }
            return sign + new_number;
        }
        else
        {
            return sign + number;
        }
    }
   
    //Do we already have the right number of decimal places?
    var existing_places = (number.length - 1) - dec_point_pos;
    if (existing_places == dec_places)
    {
        return sign + number; //If so, just return the input value
    }
   
    //Do we already have less than the number of decimal places we want?
    if (existing_places < dec_places)
    {
        //If so, pad out with zeros
        new_number = number;
        for(i=existing_places; i<dec_places; i++)
        {
            new_number += "0";
        }
        if (new_number == 0)
        {
            sign = "";
        }
        return sign + new_number;
    }
   
    //Work out whether to round up or not
    var end_pos = (dec_point_pos * 1) + dec_places;
    var round_up = false; //Whether or not to round up (add 1 to) the next digit along
    if ((number.charAt(end_pos + 1) * 1) > 4)
    {
        round_up = true;
    }
   
    //Record each digit in an array for easier manipulation
    var digit_array = new Array();
    for(i=0; i<=end_pos; i++)
    {
        digit_array[i] = number.charAt(i);
    }
   
    //Round up the last digit if required, and continue until no more 9's are found
    for(i=digit_array.length - 1; i>=0; i--)
    {
        if (digit_array[i] == ".")
        {
            continue;
        }
        if (round_up)
        {
            digit_array[i]++;
            if (digit_array[i] < 10)
            {
                break;
            }
        }
        else
        {
            break;
        }
    }
   
    //Reconstruct the string, converting any 10's to 0's (except for first digit which can stay as a 10)
    for (i=0; i<=end_pos; i++)
    {
        if (digit_array[i] == "." || digit_array[i] < 10 || i == 0)
        {
            new_number += digit_array[i];
        }
        else
        {
            new_number += "0";
        }
    }
   
    //If there are no decimal places, we don't need a decimal point
    if (dec_places == 0)
    {
        new_number = new_number.replace(".", "");
    }
   
    if (new_number == 0)
    {
        sign = "";
    }
   
    //That should do it!
    return sign + new_number;
}

 

One reader (“bram”) pointed out that the following much smaller function will work in many cases:

function cutToNumDecimals(number, numDecimals)

{
     var powerOfTen = parseInt(Math.pow(10, numDecimals));
     return number = Math.round(number * powerOfTen) / powerOfTen;
}

This is a nice elegant solution, but it does have some limitations: It cannot handle very large numbers - eg. try rounding 1234567890123456.789 to 2 decimal places. Also, it does not pad out with zeros - eg. try rounding 1.001 or 4.999 to 2 decimal places.

Comments

Leave a Reply



(Your email will not be publicly displayed.)


Captcha Code

Click the image to see another captcha.


Posted by:

Share: