Javascript .toFixed() more precise rounding alternative? - javascript

For rounding decimals (prices), I've been using .toFixed(2) for quite some time now. But I just recently discovered that Javascript can't "precisely" round decimals. I was a bit shocked that even 10.005 couldn't be rounded correctly to 10.01. It just got rounded down to 10.00. And other times it did round correctly. I like to have control over my code, so this is a big no-no for me.
And since I'm calculating prices, I think I need something more (100%) accurate for rounding only 2- or 3-decimal numbers, maybe a 4-decimal one.
Is there no straightforward way of doing basic rounding in javascript, the correct way?
UPDATE: As Felix Kling has suggested, the method of processing my prices as integers of cents, there are also drawbacks to this (besides more code)?

The reason that a number like 10.005 can't be rounded corretly is that you don't really have the number 10.005, you only have a number that is the closest possible one that can be represented using a double precision floating point variable.
The actual number that you have might be someting like 10.00499999999276253, and that would naturally round to 10.00 rather than 10.01.
To handle monetary values you should use a data type that can represent the value exactly. As numbers in Javascript are always floating point numbers, what you are left with is representing the numbers as text, and writing your own functions to do the math (or find someone who has done that already).

This should work for you, here's a fiddle to play around with it http://jsfiddle.net/5ffyC/1/
function moneyRound(flt){
var splitStr = flt.toString().split('.'),
whole = (flt * 100) | 0;
if (splitStr.length > 1 && splitStr[1].length > 2){
return splitStr[1][2] > 4? (whole + 1) / 100: whole / 100;
} else {
return flt;
}
}

Related

Rounding Mode in Number() method in Javascript

I'm rewriting the JavaScript code to Java. I came across this line in JavaScript code.
Number("<amount-values>").toFixed(0)
As the value is related to money, I'm using BigDecimal in Java in order to make accurate calculations and not to lose any precisions.
Could you please help me with the correct Rounding mode in BigDecimal that is equivalent to rounding mode of Number() method in JavaScript, where both the rounding's will produce the same result.
BigDecimal.setScale(0)
Both the scalings are producing different results when there are decimals. Created a very small table and I see that Number() method is aligning with HALF_DOWN, HALF_EVEN, HALF_UP. But this is very small example and there could be many number of other cases.
Yes, I don't want anything after the decimal point in my output.
Just to be clear, Number.prototype.toFixed() is not equivalent to Math.Round
(5.5).toFixed(0) == 6
(5.4).toFixed(0) == 5
(-5.5).toFixed(0) === **-6**
Math.round(5.5) == 6
Math.round(5.4) == 5
Math.round(-5.5) == **-5**
You should know this and decide what behavior you want, if any negative values are involved.
If all your values are positive, the logic is pretty simple: if the decimal is greater than or equal to .5 then you round up (Math.ceil), otherwise you round down (Math.floor). I believe "Math.ceil" and "Math.floor" are the method names in both JS and Java.
Since you have decimals in your example, you need to be clear if you actually want the behavior as showcased by (-5.5).toFixed(0) vs Math.round(-5.5).
It seems that toFixed effectively brings things on either side of 0 back towards 0.
Math.round on the other hand seems to round the values down, to a lower values (either less positive, or more negative).

Javascript toFixed() is not working as expected

I am using toFixed but the method does not operate as expected
parseFloat(19373.315).toFixed(2);
//19373.31 Chrome
Expected Output : 19373.32
parseFloat(9373.315).toFixed(2);
// 9373.32 Working fine
Why does the first example round down, whereas the second example round up?
The problem is that binary floating point representation of most decimal fractions is not exact. The internal representation of 19373.315 may actually be something like 19373.314999999, so toFixed rounds down, while 19373.315 might be 19373.315000001, which rounds up.
Why does the first example round down, whereas the second example round up?
Look at the binary representation of the two values in memory.
const farr = new Float64Array(2);
farr[0] = 19373.315;
farr[1] = 9373.315;
const uarr = new Uint32Array(farr.buffer);
console.log(farr[0], uarr[1].toString(2).padStart(32, 0) + uarr[0].toString(2).padStart(32, 0));
console.log(farr[1], uarr[3].toString(2).padStart(32, 0) + uarr[2].toString(2).padStart(32, 0));
Without diving into the details, we can see that the second value has an additional '1' at the end, which is lost in the first larger value when it is fit into 64 bits.
Other answers have explained why, I would suggest using a library like numeral.js which will round things as you would expect.
Assuming toFixed casts to 32-bit float;
Check with this utility...
19373.315 is stored as 19373.314453125 (an error of -0.000546875) in 32-bit floating point format.
This is despite (19373.315).toFixed(4) coming out as 19373.3150.
Even if this is "expected" or "intended", I'd still report it as a bug.
It should use a double during the rounding check, and thus proper rounding during conversion to fixed string.
I think the spec even says so. :\
In the V8 javascript engine source, the Number.prototype.toFixed function invokes DoubleToFixedCString in this file ...
There's probably some inappropriate optimization in there... (Looking into it.)
I'd suggest submitting an additional test case for V8 with 19373.315 specifically.
(19373.3150).toFixed(39) yields 19373.314999999998690327629446983337402343750.
Rounding occurs once to bring it up to 19373.315 - which is correct - but not at the right digit when rounding to 2 digits.
I think this should have a second pass on rounding here to catch edge cases like this. I think it might have to round to n+1 digits, then again to n digits. Maybe there's some other clever way to fix it though.
function toFixedFixed(a,n) {
return (a|0) + parseFloat((a % 1).toFixed(n+1)).toFixed(n).substr(1);
}
console.log(toFixedFixed(19373.315,2)); // "19373.32"
console.log(toFixedFixed(19373.315,3)); // "19373.315"
console.log(toFixedFixed(19373.315,4)); // "19373.3150"
console.log(toFixedFixed(19373.315,37)); // "19373.3149999999986903276294469833374023438"
console.log(toFixedFixed(19373.315,38)); // "19373.31499999999869032762944698333740234375"
console.log(toFixedFixed(19373.315,39)); // "19373.314999999998690327629446983337402343750"
(Adopted from my comments on Vahid Rahmani's answer, who is correct.)

JS floating point causing incorrect rounding

I have what may be an edge case scenario. When trying to round the value 4.015 to 2 decimal places, I always end up with 4.01 instead of the expected 4.02. This happens consistently for all numbers with .015 as the decimal portion.
I round using a fairly common method in JS:
val = Math.round(val * 100) / 100;
I think the problem starts when multiplying by 100. The floating point inaccuracy causes this value to be rounded down rather than up.
var a = 4.015, // 4.015
mult = a * 100, // 401.49999999999994 (the issue)
round = Math.round(mult), // 401
result = round / 100; // 4.01 (expected 4.02)
Fiddle: http://jsfiddle.net/eVXRL/
This problem does not happen if I try to round 4.025. The expected value of 4.03 does return; it's only an issue with .015 (so far).
Is there a way to elegantly resolve this? There is of course the hack of just looking for .015 and handling that case one-off, but that just seems wrong!
I ended up using math.js to do mathematical operations and that solved all my floating point issues.
The advantage of this lib was that there was no need to instantiate any sort of Big Decimal object (even though the lib does support BigDecimal). It was just as simple as replacing Math with math and passing the precision.
Floating point numbers are not real numbers, they are floating point numbers.
There are infinite number of real numbers, but only finite number of bits to represent them, thus sometimes, there must be some rounding error if the exact number you want cannot be represented in the floating point system.
Thus, when dealing with floating point numbers, you must take into consideration, that you won't have the exact same number you had in mind.
If you need an exact number, you should use a library that gives you better precision, usually it will be using a fixed point, and/or symblic representation
More information can be found in the wikipedia page, and in this (a bit complex, but important) article: What Every Computer Scientist Should Know About Floating-Point Arithmetic
If you are going to work with numbers as decimals, then use a decimal library, like big.js.
Floating point values in most languages (including javascript) are stored in a binary representation. Mostly, that does what you expect. In circumstances like this, your 4.015 is converted to a binary string, and happens to get encoded as the 4.014999999... value you saw, which is the closest binary representation available in a double precision (8-byte) IEEE754 value.
If you are doing financial math, or math for human consumption (i.e. as decimals), then you will want 4.015 to round to 4.02, and you need a decimal library.
There are plans to include decimal representation of floating point values in javascript (e.g. here), since the new IEEE754-2008 standard includes decimal32 etc as decimal floating point value representations. For more read here: http://speleotrove.com/decimal/
Finally, if you are doing accounting maths in javascript (i.e. financial calculations which should not accidentally create or disappear money), then please do all calculations in whole cents/pence.
You can use a regexp to extract and replace the digits to get what you want :
val = (val + "").replace(/^([0-9]+\.[0-9])([0-9])([0-9]).*$/, function(whole, head, lastdigit, followup) {
if(followup >= 5) {
return head + ("" + (parseInt(lastdigit) + 1));
}else return head + lastdigit;
});
Otherwise you can use val = val.toFixed(2) but the value specific 4.015 gives 4.01 (4.0151 gives 4.02 as "expected").

toFixed javascript function giving strange results?

I am trying to fix the number to 2 digits after decimal and for that i am using toFixedfunction of javascript. Below are the strange results i am getting, please check and help me.
var number = 11.995;
number.toFixed(2); // giving me 11.99 which is correct
var number = 19.995;
number.toFixed(2); // giving me 20.00 which is incorrect
Can anyone tell me why it is happening.
Thanks for your help.
This is how floating point math works. The value 19.995 is not exact binary (base 2). To make it more clear, think of an exact number when you divide 10/3.
For more in-depth explanations, read this: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
In your case you can work with strings instead (at least it seems like that is what you want):
number.toString().substr(0, n);
Or define a function like this (made in 2 minutes, just an example):
Number.toFixed = function(no, n) {
var spl = no.toString().split('.');
if ( spl.length > 1 ) {
return spl[0]+'.'+spl[1].substr(0,n);
}
return spl[0];
}
Number.toFixed(19.995, 2); // 19.99
toFixed rounds the value. Since 19.995 is exactly halfway between 19.99 and 20.00, it has to choose one of them. Traditionally, rounding prefers the even result (this prevents bias, since round-ups and round-downs will be equal).
I have create a function which done all for me..
function toFixed(number, precision) {
var multiplier = Math.pow(10, precision + 1),
wholeNumber = Math.floor(number * multiplier);
return Math.round(wholeNumber / 10) * 10 / multiplier;
}
//Call this function to retrive exect value
toFixed((+adjustmentval), 2);
David has answered your doubt I'm guessing. Just providing an alternate solution here.
You can use the Math.floor() method of the Math object for this.
Something like this, Math.floor(number*100)/100
Can anyone tell me why it is happening.
The IEEE-754 double-precision binary floating point number standard used by JavaScript's number type (and similar times in several other languages) does not perfectly store all numbers, it stores some numbers imprecisely, in a way that lets it A) Store them in just 64 bits, and B) Calculate with them quickly.
For 11.995, the actual value is 11.99499988555908203125, just slightly less than 11.995.
For 19.995, the actual value is 19.9950008392333984375, just slightly more than 19.995.
That explains why when you round them using the usual round-to-nearest-half-up operation, 11.995 (which is really 11.99499988555908203125) rounds down to 11.99 but 19.995 (which is really 19.9950008392333984375) rounds up to 20.00.
(This site has a handy calculator for visualizing this stuff.)
More here on SO:
Is floating point math broken?
How to deal with floating point number precision in JavaScript?

Javascript issue with math calculations

Why is it if I do this in javascript, I get the following result:
1234.56 * 10 = 12345.599999999999
It should be 123456. How can I get around this problem?
Thanks.
Floating points are not exact, since there are ifinite numbers at their range [or in any range to be more exact], and only a finite number of bits to store this data.
Have a look at what every programmer should know about floating point arithmetics.
Another easy solution:
parseFloat((1234.56 * 10).toPrecision(12))
and the result will be: 12345.6, and YES... it works with decimal numbers.
As the others said, floating points and so on.
Easy solution would be to do something like this:
var answer = parseInt(1234.56 * 10);
Or just use Math.round?
All numbers in JS are internally defined by float and drop the less significant digits if needed.
(10000000000000000000000000000 + 1) == 10000000000000000000000000000
// this will return true
And javascript is well known for droping bits quite often in numbers. So handle with care

Categories

Resources