Javascript multiplying incorrectly, causing incorrect rounding - javascript

When I pull the values I want to multiply, they're strings. So I pull them, parse them as floats (to preserve the decimal places), and multiply them together.
LineTaxRate = parseFloat(myRate) * parseFloat(myQuantity) * parseFloat(myTaxRateRound);
This has worked for 99% of my invoices but I discovered one very odd problem.
When it multiplied: 78 * 7 * 0.0725
Javascript is returning: 39.584999999999994
When you normally do the math in a calculator its: 39.585
When all is said and done, I take that number and round it using .toFixed(2)
Because Javascript is returning that number, it's not rounding to the desired value of: $39.59
I tried Math.round() the total but I still get the same number.
I have thought of rounding the number to 3 decimals then two, but that seems hacky to me.
I have searched everywhere and all I see is people mention parseFloat loses its precision, and to use .toFixed, however in the example above, that doesn't help.
Here is my test script i made to recreate the issue:
<script>
var num1 = parseFloat("78");
var num2 = parseFloat("7");
var num3 = parseFloat("0.0725");
var myTotal = num1 * num2 * num3;
var result = Math.round(myTotal*100)/100
alert(myTotal);
alert(myTotal.toFixed(2));
alert(result);
</script>

Floating points are represented in binary, not decimal. Some decimal numbers will not be represented precisely. And unfortunately, since Javascript only has one Number class, it's not a very good tool for this job. Other languages have decent decimal libraries designed to avoid precisely this kind of error. You're going to have to either accept one-cent errors, implement a solution server-side, or work very hard to fix this.
edit: ooh! you can do 78 * 7 * 725 and then divide by 10000, or to be even more precise just put the decimal point in the right place. Basically represent the tax rate as something other than a tiny fraction. Less convenient but it'll probably take care of your multiplication errors.

You might find the Accounting.js library useful for this. It has an "improved" toFixed() method.

JavaScript/TypeScript have only one Number class which is not that good. I have the same problem as I am using TypeScript. I solved my problem by using decimal.js-light library.
new Decimal(78).mul(7).mul(0.0725) returns as expected 39.585

Related

Rounding up or down when 0.5

I am having an issue with the way Javascript is rounding numbers when hitting 0.5.
I am writing levies calculators, and am noticing a 0.1c discrepancy in the results.
The problem is that the result for them is 21480.705 which my application translates into 21480.71, whereas the tariff says 21480.70.
This is what I am seeing with Javascript:
(21480.105).toFixed(2)
"21480.10"
(21480.205).toFixed(2)
"21480.21"
(21480.305).toFixed(2)
"21480.31"
(21480.405).toFixed(2)
"21480.40"
(21480.505).toFixed(2)
"21480.51"
(21480.605).toFixed(2)
"21480.60"
(21480.705).toFixed(2)
"21480.71"
(21480.805).toFixed(2)
"21480.81"
(21480.905).toFixed(2)
"21480.90"
Questions:
What the hell is going on with this erratic rouding?
What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?
So as some of the others already explained the reason for the 'erratic' rounding is a floating point precision problem. You can investigate this by using the toExponential() method of a JavaScript number.
(21480.905).toExponential(20)
#>"2.14809049999999988358e+4"
(21480.805).toExponential(20)
#>"2.14808050000000002910e+4"
As you can see here 21480.905, gets a double representation that is slightly smaller than 21480.905, while 21480.805 gets a double representation slightly larger than the original value. Since the toFixed() method works with the double representation and has no idea of your original intended value, it does all it can and should do with the information it has.
One way to work around this, is to shift the decimal point to the number of decimals you require by multiplication, then use the standard Math.round(), then shift the decimal point back again, either by division or multiplication by the inverse. Then finally we call toFixed() method to make sure the output value gets correctly zero-padded.
var x1 = 21480.905;
var x2 = -21480.705;
function round_up(x,nd)
{
var rup=Math.pow(10,nd);
var rdwn=Math.pow(10,-nd); // Or you can just use 1/rup
return (Math.round(x*rup)*rdwn).toFixed(nd)
}
function round_down(x,nd)
{
var rup=Math.pow(10,nd);
var rdwn=Math.pow(10,-nd);
return (Math.round(x*-rup)*-rdwn).toFixed(nd)
}
function round_tozero(x,nd)
{
return x>0?round_down(x,nd):round_up(x,nd)
}
console.log(x1,'up',round_up(x1,2));
console.log(x1,'down',round_down(x1,2));
console.log(x1,'to0',round_tozero(x1,2));
console.log(x2,'up',round_up(x2,2));
console.log(x2,'down',round_down(x2,2));
console.log(x2,'to0',round_tozero(x2,2));
Finally:
Encountering a problem like this is usually a good time to sit down and have a long think about wether you are actually using the correct data type for your problem. Since floating point errors can accumulate with iterative calculation, and since people are sometimes strangely sensitive with regards to money magically disappearing/appearing in the CPU, maybe you would be better off keeping monetary counters in integer 'cents' (or some other well thought out structure) rather than floating point 'dollar'.
The why -
You may have heard that in some languages, such as JavaScript, numbers with a fractional part are calling floating-point numbers, and floating-point numbers are about dealing with approximations of numeric operations. Not exact calculations, approximations. Because how exactly would you expect to compute and store 1/3 or square root of 2, with exact calculations?
If you had not, then now you've heard of it.
That means that when you type in the number literal 21480.105, the actual value that ends up stored in computer memory is not actually 21480.105, but an approximation of it. The value closest to 21480.105 that can be represented as a floating-point number.
And since this value is not exactly 21480.105, that means it is either slightly more than that, or slightly less than that. More will be rounded up, and less will be rounded down, as expected.
The solution -
Your problem comes from approximations, that it seems you cannot afford. The solution is to work with exact numbers, not approximate.
Use whole numbers. Those are exact. Add in a fractional dot when you convert your numbers to string.
This works in most cases. (See note below.)
The rounding problem can be avoided by using numbers represented in
exponential notation:
function round(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
console.log(round(21480.105, 2).toFixed(2));
Found at http://www.jacklmoore.com/notes/rounding-in-javascript/
NOTE: As pointed out by Mark Dickinson, this is not a general solution because it returns NaN in certain cases, such as round(0.0000001, 2) and with large inputs.
Edits to make this more robust are welcome.
You could round to an Integer, then shift in a comma while displaying:
function round(n, digits = 2) {
// rounding to an integer is accurate in more cases, shift left by "digits" to get the number of digits behind the comma
const str = "" + Math.round(n * 10 ** digits);
return str
.padStart(digits + 1, "0") // ensure there are enough digits, 0 -> 000 -> 0.00
.slice(0, -digits) + "." + str.slice(-digits); // add a comma at "digits" counted from the end
}
What the hell is going on with this erratic rouding?
Please reference the cautionary Mozilla Doc, which identifies the cause for these discrepancies. "Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results..."
Also, please reference Is floating point math broken? (Thank you Robby Cornelissen for the reference)
What's the quickest easiest way to get a "rounded up" result (when hitting 0.5)?
Use a JS library like accounting.js to round, format, and present currency.
For example...
function roundToNearestCent(rawValue) {
return accounting.toFixed(rawValue, 2);
}
const roundedValue = roundToNearestCent(21480.105);
console.log(roundedValue);
<script src="https://combinatronics.com/openexchangerates/accounting.js/master/accounting.js"></script>
Also, consider checking out BigDecimal in JavaScript.
Hope that helps!

Javascript scientific notation floats vs integers explained

I am working with js numbers and have lack of experience in that. So, I would like to ask few questions:
2.2932600144518896
e+160
is this float or integer number? If it's float how can I round it to two decimals (to get 2.29)? and if it's integer, I suppose it's very large number, and I have another problem than.
Thanks
Technically, as said in comments, this is a Number.
What you can do if you want the number (not its string representation):
var x = 2.2932600144518896e+160;
var magnitude = Math.floor(Math.log10(x)) + 1;
console.log(Math.round(x / Math.pow(10, magnitude - 3)) * Math.pow(10, magnitude - 3));
What's the problem with that? Floating point operation may not be precise, so some "number" different than 0 should appear.
To have this number really "rounded", you can only achieve it through string (than you can't make any operation).
JavaScript only has one Number type so is technically neither a float or an integer.
However this isn't really relevant as the value (or rather representation of it) is not specific to JavaScript and uses E-Notation which is a standard way to write very large/small numbers.
Taking this in to account 2.2932600144518896e+160 is equivalent to 2.2932600144518896 * Math.pow(10,160) and approximately 229 followed by 158 zeroes i.e. very flippin' big.

Work around for Javascript's Floating Point Arithmetic

I started having problems with decimals which made me learn about the whole floating point math. My question is, what's a viable solution for it?
x = 0.1;
y = 0.2;
num = x + y;
num = Math.round(num * 100) / 100;
or
x = 0.1;
y = 0.2;
num = x + y;
num = num.toFixed(2);
num = Number(num);
Are these both 100% viable options? As in, never have to worry about having the same problem anymore? Which one would you recommend? Or would you recommend a different solution? Any reason to use one solution over the other? Thanks in advance for any help.
EDIT:
Sorry I wasn't more specific. I'm fine with it always being 2 decimals, since that won't be a problem for my project. Obviously if you want more decimals you would use 1000 instead of 100 and toFixed(3), and so on. My main concern is, are the above 2 solutions 100% viable, as in, I won't have to worry about any of the same problems? And also, would you recommend the first solution or the second? Or another one altogether? Since I will be using a method quite a lot for many calculations. Thanks again for your help.
This is not a problem with JavaScript's floating point implementation, or something that will go away if you use a string formatting function like toFixed (the MDN docs for it here make clear that it is a string representation returned, not some other format of number). Rather, this is an inherent property of floating point arithmetic as a concept - it has a variable accuracy designed to closely approximate values within a certain range.
If you want your values to always be entirely accurate, the only solution is not to use floating point numbers. Generally, this is done by using integers representing some fraction of the "whole" numbers you're dealing with (e.g. pence/cents instead of pounds/euros/dollars, or milliseconds instead of seconds). Alternatively, you may be able to find a precision maths library which performs fixed-point arithmetic, so avoids the inaccuracies but will have worse performance.
If you don't mind the risk of the inaccuracies slowly building up, you can simply use formatting functions to only display to a certain precision when you output the result of a calculation. There is little point in converting to a string with a fixed precision and then back to a number, as the floating point implementation may still be unable to represent that number with complete precision.

Better way of rounding off or rounding up in javascript

I have this problem with rounding up or rounding off with javascript. Well it gets the job done but there are some scenarios that it fails to do the job properly.
For example.
The price is 0.30 with a 15% discount should have 0.255 or 0.26 as result. (this is somehow okay)
The price is 79.99 with a 50% discount should have 40 as result. (actual result is 39.995)
Tried using this as reference Using toFixed(2) and math round to get correct rounding
also did this process Math.round(value*Math.pow(10,dec))/Math.pow(10,dec);
Any ideas?
Thanks
This is my initial test. Though this is just an initial test what I did is checked if the last two digits is greater than 5 then apply Math.ceil that would to the 0.255 to be 0.26 but when using 39.99 the result would still be 39.99 though 39.99 should use Math.round so that the result would be 40.
What I would like to accomplish is to be able to round up or round down any number if it needs rounding up or if it needs rounding up.
UPDATE
I was able to solve my own problem. (Atleast to my specific environment)
http://jsfiddle.net/xMj3M/4/
This is because of how floats are handled. I would suggest having your original numbers in integers only, so you would start with 7999. If you use this method, you will get the correct result.
In general, especially when dealing with money, it is always a good idea to work in pennies first, and then output it in the format of the currency.
You could try:
var someNumber = 36.7327;
var someNumberRoundedTo2Decimals = (Math.round((someNumber * 100)) / 100);
// = 36.73
basically this moves the decimal to the right 2 digits..
3673.27
then rounds it off to an int
3673
then moves the decimal back to the left 2 places
36.73

round in javascript stuck me

hello I want to round an amount in javascript but unable to do and totally stuck.
My scenario is, I have a base amount and marukup % on base. On the basis of these two I want to calculate total amount. I have calculated the amount but i want to round the result.
I have used following formula
amount=base+((base/100)*markup
This formula always give me result without decimal point. I want to get exact amount upto two decimal points. I have used math.round like this
amount=math.round(base+((base/100)*markup).toFixed(2)
but it always return result without decimal point. For example my base value is 121 and markup is 5%. The amount should be 127.05 . But above formula always returns 127. Any guidelines?
I'm pretty sure math.round returns an integer. Even if you round it then, it'll just be 127.00 anyway.
Here's the correct solution(but it isn't easy):
Do not use non-integer values for money!
It doesn't work.
Use an integer in cents.
That is, instead of 127, keep 12700 in your app.
That way all roundings should work fine.
The toFixed(n) function rounds the Number to n decimals, there is no need to use Math.round at all. Try:
total = function (base, markup) { return (base + (base * markup / 100)); };
amount = total(121,5).toFixed(2);
Note that amount will be typeof String and not Number.
This should work:
amout = (Math.round((base*(1+markup))*100)/100).toFixed(2)
By the way, i was using markup as 5/100...
Sound like a integer division problem to me. I'd guess that javascript is seeing the 100 as an int.
Try this:
amount=(base+((base/100.toFixed(2))*markup).toFixed(2)

Categories