7

I'm facing an issue with Math.floor function of javascript for the below scenario:

1) from the value betwwen 8192 and 10484,

    if I type 8192.8  -> The Math.floor converts it into 8192.79

    if I type 8192.88  -> The Math.floor converts it into 8192.87

    if I type 8192.3  -> The Math.floor converts it into 8192.29

Strange part is that except from the range given above the function works fine.

HTML:
<div data-bind="text: popIncrease"></div>
<input type="text" data-bind="value: userInput,  valueUpdate: 'afterkeydown'" />

Javascript:

var ViewModel = function () {
var _self = this;
_self.userInput = ko.observable();
_self.popIncrease = ko.computed(function () {

        return parseFloat((Math.floor(_self.userInput() * 100) / 100)).toFixed(2);
});
};

ko.applyBindings(new ViewModel());

jsfiddle:https://jsfiddle.net/91z5bdy4/1/

When I changed 100 with 1000 it solved the error but I do not understand why this happened on the first place?

ron_meh
  • 93
  • 1
  • 6
  • 3
    possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – suish May 14 '15 at 01:40
  • Actually has nothing to do with knockout or jquery. What are you actually trying to do? It seem like you want to round a number to 2 decimal places then display it with 2 decimal places. Passing a number to *parseFloat* is redundant (it's already a number). An expression shouldn't be wrapped in double brackets like `((…))`, one set is redundant. Why not `return parseFloat(_self.userInput()).toFixed(2)`? – RobG May 14 '15 at 01:43
  • 8192.8 * 100 === 819279.9999999999, good luck with floating point arithmetics. – floribon May 14 '15 at 01:45
  • See [*How do I format a Number as a String with exactly 2 decimal places?*](http://www.jibbering.com/faq/#formatNumber). – RobG May 14 '15 at 01:54

4 Answers4

1

You can just switch to this:

return parseFloat(_self.userInput()).toFixed(2);

Working version of your jsFiddle: https://jsfiddle.net/jfriend00/5rLL04Lk/


Or, if you want to work around some of the idiosyncrasies of .toFixed(), you can use this:

return (Math.round(_self.userInput() * 100) / 100).toFixed(2);

Working jsFiddle: https://jsfiddle.net/jfriend00/xx2aj2L0/

This solution passes all three of your test cases.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Except that the rounding in *toFixed* is buggy… `(3.335).toFixed(2)` gives '3.33' where '3.34' might be expected. – RobG May 14 '15 at 01:55
  • @RobG - offered another solution that works around that `.toFixed(2)` weirdness. – jfriend00 May 14 '15 at 02:02
  • Of course there's still the ancient bug in IE's implementation of *toFixed* (jScript 5.8), but perhaps it's fixed now. ;-) – RobG May 14 '15 at 02:06
  • `8192.809` converts to `8192.81`, is that the desired result, or is supposed to be `8192.80`? – John S May 14 '15 at 02:14
1

It's not Math.floor() that causes the problem, it is the inexactness of the floating point arithmetic. When you multiply 8192.8 by 100, you get 819279.9999999999.

Perhaps you should just manipulate it as a string:

function floorString(str) {
    var pos = str.indexOf('.');
    return (pos >= 0) ? ((str + '00').slice(0, pos + 3)) : (str + '.00');
}

jsfiddle

John S
  • 21,212
  • 8
  • 46
  • 56
0

The order of your floor/parse seems out of order to me.

Try:

return Math.floor(parseFloat(_self.userInput())).toFixed(2);

Though be aware that 1.999999999999999999999999999999 gives 2.00 using the above; this is because floating point numbers aren't able to represent all values precisely.

Paul
  • 1,502
  • 11
  • 19
  • 1.999999999 gives 2.00 because `.toFixed(2)` rounds to two decimal places. There are issues with floating point precision, but your example is not where that comes into play. – jfriend00 May 14 '15 at 01:53
  • @jfriend00 It's probably more to do with `parseFloat("1.9999999999999999999999999") === 2` being `true`. – Paul May 14 '15 at 01:56
  • @Paul—and `(1.999).toFixed(2) === '2.00'`. ;-) – RobG May 14 '15 at 02:05
  • @RobG While that's true, the Math.floor(1.999) in the code above would result in 1 being passed to .toFixed, resulting in 1.00 - yes? – Paul May 14 '15 at 02:10
  • @Paul—Yes. [SO limit]. – RobG May 14 '15 at 12:03
0

another one without using a Math function (2 lines without formatting)

function floorString(str) {
    var matches = str.match(/([\d]+(\.[\d]{0,2}))/);
    return matches === null || matches[2].length === 1 ?
            (str + ".00").replace("..", ".") :
            matches[2].length < 3 ?
                matches[0] + "00".substr(3 - matches[2].length) :
                matches[0];
}
chiliNUT
  • 18,989
  • 14
  • 66
  • 106