Domanda

This is the rounding function we are using (which is taken from stackoverflow answers on how to round). It rounds half up to 2dp (by default)

e.g. 2.185 should go to 2.19

function myRound(num, places) {
    if (places== undefined) {
        // default to 2dp
        return  Math.round(num* 100) / 100;
    }
    var mult = Math.pow(10,places);

    return  Math.round(num* mult) / mult;
}

It has worked well but now we have found some errors in it (in both chrome and running as jscript classic asp on IIS 7.5).

E.g.:

alert(myRound(2.185));      // = 2.19
alert (myRound(122.185));   // = 122.19
alert (myRound(511.185));   // = 511.19
alert (myRound(522.185));   // = 522.18  FAIL!!!!
alert (myRound(625.185));   // = 625.18  FAIL!!!!

Does anyone know:

  1. Why this happens.
  2. How we can round half up to 2 dp without random rounding errors like this.

update: OK, the crux of the problem is that in js, 625.185 * 100 = 62518.499999 How can we get over this?

È stato utile?

Soluzione 3

OK, found a "complete" solution to the issue.

Firstly, donwnloaded Big.js from here: https://github.com/MikeMcl/big.js/

Then modified the source so it would work with jscript/asp:

/* big.js v2.1.0 https://github.com/MikeMcl/big.js/LICENCE */
var Big = (function ( global ) {
    'use strict';
:
// EXPORT
return Big;
})( this );

Then did my calculation using Big types and used the Big toFixed(dp), then converted back into a number thusly:

var bigMult = new Big (multiplier);
var bigLineStake = new Big(lineStake);
var bigWin = bigLineStake.times(bigMult);
var strWin = bigWin.toFixed(2);  // this does the rounding correctly.
var win = parseFloat(strWin);  // back to a number!

This basically uses Bigs own rounding in its toFixed, which seems to work correctly in all cases.

Shame Big doesnt have a method to convert back to a number without having to go through a string.

Altri suggerimenti

Your problem is not easily resolved. It occurs because IEEE doubles use a binary representation that cannot exactly represent all decimals. The closest internal representation to 625.185 is 625.18499999999994543031789362430572509765625, which is ever so slightly less than 625.185, and for which the correct rounding is downwards.


Depending on your circumstances, you might get away with the following:

Math.round(Math.round(625.185 * 1000) / 10) / 100    // evaluates to 625.19

This isn't strictly correct, however, since, e.g., it will round, 625.1847 upwards to 625.19. Only use it if you know that the input will never have more than three decimal places.


A simpler option is to add a small epsilon before rounding:

Math.round(625.185 * 100 + 1e-6) / 100

This is still a compromise, since you might conceivably have a number that is very slightly less than 625.185, but it's probably more robust than the first solution. Watch out for negative numbers, though.

Try using toFixed function on value. example is below:

var value = parseFloat(2.185);
var fixed = value.toFixed(2);
alert(fixed);

I tried and it worked well.

EDIT: You can always transform string to number using parseFloat(stringVar).

EDIT2:

function myRound(num, places) {
    return parseFloat(num.toFixed(places));
}

EDIT 3:

Updated answer, tested and working:

function myRound(num, places) {
    if (places== undefined) {
     places = 2;
    }
    var mult = Math.pow(10,places + 1);
    var mult2 = Math.pow(10,places);
    return  Math.round(num* mult / 10) / mult2;
}

EDIT 4:

Tested on most examples noted in comments:

function myRound(num, places) {
    if (places== undefined) {
     places = 2;
    }
    var mult = Math.pow(10,places);
    var val = num* mult;
    var intVal = parseInt(val);
    var floatVal = parseFloat(val);

    if (intVal < floatVal) {
        val += 0.1;
    }
    return Math.round(val) / mult;
}

EDIT 5: Only solution that I managed to find is to use strings to get round on exact decimal. Solution is pasted below, with String prototype extension method, replaceAt. Please check and let me know if anyone finds some example that is not working.

function myRound2(num, places) {
    var retVal = null;
    if (places == undefined) {
        places = 2;
    }

    var splits = num.split('.');
    if (splits && splits.length <= 2) {
        var wholePart = splits[0];
        var decimalPart = null;
        if (splits.length > 1) {
            decimalPart = splits[1];
        }
        if (decimalPart && decimalPart.length > places) {
            var roundingDigit = parseInt(decimalPart[places]);
            var previousDigit = parseInt(decimalPart[places - 1]);
            var increment = (roundingDigit < 5) ? 0 : 1;
            previousDigit = previousDigit + increment;
            decimalPart = decimalPart.replaceAt(places - 1, previousDigit + '').substr(0, places);
        }
        retVal = parseFloat(wholePart + '.' + decimalPart);
    }

    return retVal;
}

String.prototype.replaceAt = function (index, character) {
    return this.substr(0, index) + character + this.substr(index + character.length);
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top