Javascript floating point addition workaround [closed] - javascript

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
Ok, so we all know of the floating point number problem, such as:
0.23 - 1 + 1 = 0.22999999999999998
And since in javascript, unlike other languages, all numbers are actually floating points and there's no int/decimal there are all kinds of libraries and workarounds such as BigDecimal to handle this problem. This is best discussed here.
I was creating a "numeric spinner" control that supports floating point numbers, and obviously I wouldn't want the user to click "up" and then "down" and get a different number from what he started with, so I tried writing a workaround - a "addPrecise" method of sorts.
My idea was the following:
Take the two numbers I'm about to add, and figure out their "precision" - how many digits they have after the decimal point.
Add the two numbers as floats
Apply toFixed() on the result with the max precision of the two
For example:
float #1: 1.23
float #2: -1
adding them normally would result in 0.22999999999999998 but if I'm taking the maximal number of decimal places, which is #1's 2 digits, and apply toFixed(2) I get 0.23 as I wanted.
I've done this with the following piece of code but I'm not happy with it.
function addPrecise(f1, f2){
var floatRegex = /[^\d\-]/;
var f1p = floatRegex.exec(f1.toString()) ? f1.toString().split(floatRegex.exec(f1.toString()))[1].length : 0;
var f2p = floatRegex.exec(f2.toString()) ? f2.toString().split(floatRegex.exec(f2.toString()))[1].length : 0;
var precision = Math.max(f1p,f2p);
return parseFloat((parseFloat(f1) + parseFloat(f2)).toFixed(precision));
}
(It's worth noting that I'm using the regex to find the 'floating point' because in other locales it might be a comma instead of a period. I'm also taking into account the possibility that I got an Int (with no point) in which case it's precision is 0.)
Is there a cleaner/simpler/better way to do this?
Edit: I'd also like to point out that finding a reliable way to extract the number of decimal digits is also part of the question. I'm unsure about my method there.

An appropriate solution for this problem is:
Determine how many digits, say d, are needed after the decimal point.
Read inputs from the user and convert them to integers scaled by 10d.
Do all arithmetic using the integers.
When displaying values for the user, divide them by 10d for display.
More details:
If all work is in whole units of user-entered data, then the number of digits needed after the decimal point, d, is the same as the number of digits to be accepted from the user. (If one were going to do some arithmetic with fractions of the step size, for example, then more digits might be needed.)
When the user enters input, it should be converted to the scaled integers. Note that the user enters a string (not a floating-point number; it is just a string when the user types it), and the string should be a numeral (characters that represent a number). One way to process this input is to call library routines to convert this numeral to a floating-point number, then multiply by 10d, then round the floating-point result to the nearest integer (to compensate for floating-point rounding errors). For numbers of reasonable size, this will always produce exactly the desired result; floating-point rounding errors will not be a problem. (Excessively large user input should be rejected.) Another way to process this input is to write your own code to convert the numeral directly to a scaled integer, avoiding floating-point entirely.
As long as only addition, multiplication, and subtraction are used (e.g., multiplying the step size by a number of steps and adding to a prior value), and the integer range is not exceeded, then all arithmetic is exact; there are no rounding errors. If division is used, this approach must be reconsidered.
As with reading input, displaying output can be performed either with an assist from floating point and library routines or by writing your own code to convert directly. To use floating-point, convert the integer to floating-point, divide by 10d, and use a library routine to format it with no more than d digits after the decimal place. (Using default format conversions in library routines might result in more than d digits displayed. If only d digits are displayed, and d is a reasonably small number, then floating-point rounding errors that occur during the formatting will not be visible.)
It is possible to implement all of the above using only floating-point, as long as care is taken to ensure that the arithmetic uses entirely integers (or other values that are exactly representable in the floating-point format) within reasonable bounds.

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!

Angular JS inaccurate substraction of two decimal numbers

In my angular JS application I have three amounts in one object. In Chrome browser debugger i have following:
payment.amountForClosing = payment.amountRemaining - payment.amountReserved;
payment.amountRemaining has a value of 3026.2
payment.amountReserved has a value of 2478.4
after substraction payment.amountForClosing has a value of 547.7999999999997, and 547.8 is displayed.
When user tries to make another payment closing, my validation logic returns an error indicating that there is not enough money to make payment closing because of state presented above.
Those amount values come from C# WebApi 2.0 backend, as System.Decimal types.
When dealing with currency, worst thing that you can do is use floating point numbers, as you can see. Because of way how floats are stored, some operations may provide incorrect results. Only thing that will always be correct is multiplication/division by power of 10.
Best way is to store currency as Integer/BigInteger, or whatever, and save it with e.g. 4 decimal places so 12100 should represent 1.21
Then you can do subtraction/multiplication of integer numbers and at the end divide by power of 10.
My suggestion is that you transform those numbers to integer before any operation then divide it back to float.
Floating point precision in Javascript has been discussed before (eg. here or here). You have the following options:
Convert all your numbers to integers.
Format the result to a fixed number of significant digits using .toFixed(2)
Use a special datatype for decimals like BigDecimal
Out of all of these I agree with #PerunSS that in your case, the best option would be converting the numbers to integers before any operation, and the converting them back.

Summing numbers javascript [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 9 years ago.
'-15.48' - '43'
Just wrote this in console and result is the following:
-58.480000000000004
Why Is it so? And what to do to get correct result?
Because all floating point math is like this and is based on the IEEE 754 standard. JavaScript uses 64-bit floating point representation, which is the same as Java's double.
to fix it you may try:
(-15.48 - 43).toFixed(2);
Fiddle demo
use: toFixed()
var num = 5.56789;
var n=num.toFixed(2);
result:5.57
http://en.wikipedia.org/wiki/Machine_epsilon
Humans count in decimal numbers, machines mostly use binary. 10 == 2x5; 2 and 5 are mutually prime numbers. That trivial fact has an unpleasant consequence though.
In most calculations (except lucky degenerate cases) with "simple" decimal numbers, that include division, the result would be an indefinite repeating binary number.
In most calculations (except lucky degenerate cases) with "simple" binary numbers, that include division, the result would be an indefinite repeating decimal number.
One can check this using pen and pencil as described http://en.wikipedia.org/wiki/Repeating_decimal#Every_rational_number_is_either_a_terminating_or_repeating_decimal
That means that almost every time you see the the result of floating point calculations on your screen - as a finite number - the computer somewhat cheats at you and shows you some approximation instead of the real result.
That means that almost every time you store the results of the calculation in the variable - which has a finite size, not any larger than the computer's available memory - the computer somewhat cheats at you and retains some approximation instead of the real result.
The typical gotchas then may include.
Program is accounting for the sum of some looong sequence, an infinite loop of x AVG := AVG + X[i]; kind where 0 < X[i] < const. If the loop would run long enough, you would see that at some point AVG no more changes the value, all the elements fro mthatpoint further on are just discarded.
Program calculates some value twice using different formulas and then makes a safety check like Value_1 == Value_2. For theoretical mathematics the values are equal, for the real computers they are not.

Compute the double value nearest preferred decimal result

Let N(x) be the value of the decimal numeral with the fewest significant digits
such that x is the double value nearest the value of the numeral.
Given double values a and b, how can we compute the double value nearest N(b)-N(a)?
E.g.:
If a and b are the double values nearest .2 and .3,
the desired result is the double value nearest .1,
0.1000000000000000055511151231257827021181583404541015625,
rather than than the result of directly subtracting a and b,
0.09999999999999997779553950749686919152736663818359375.
As a baseline: In Java, the Double.toString() provides the N(x) function described in the question, returning its value as a numeral. One could take the strings for a and b, subtract them with the elementary-school method, and convert the resulting string to double.
This demonstrates solving the problem is quite feasible using existing library routines. This leaves the task of improving the solution. I suggest exploring:
Is there a function D(x) that returns the number of significant digits after the decimal place for the numeral described in N(x)? If so, can we multiply a and b by a power of ten determined by D(a) and D(b), round as necessary to produce the correct integer results (for situations where they are representable as double values), subtract them, and divide by the power of ten?
Can we establish criteria for which b-a or some simple expression can be quickly rounded to something near a decimal numeral, bypassing the code that would be necessary for harder cases? E.g., could we prove that for numbers within a certain range, (round(10000*b)-round(10000*a))/10000 always produces the desired result?
You can convert to 'integers' by multiplying then dividing by a power of ten:
(10*.3 - 10*.2)/10 == 0.1000000000000000055511151231257827021181583404541015625
It may be possible to work out the appropriate power of ten from the string representation of the number. #PatriciaShanahan suggests looking for repeated 0's or 9's.
Consider using a BigDecimal library such as javascript-bignum instead.
You could also inquire in Smalltalk Pharo 2.0 where your request translates:
^(b asMinimalDecimalFraction - a asMinimalDecimalFraction) asFloat
Code could be found as attachment to issue 4957 at code.google.com/p/pharo/issues - alas, dead link, and the new bugtracker requires a login...
https://pharo.fogbugz.com/f/cases/5000/Let-asScaledDecimal-use-the-right-number-of-decimals
source code is also on github, currently:
https://github.com/pharo-project/pharo-core/blob/6.0/Kernel.package/Float.class/instance/printing/asMinimalDecimalFraction.st
The algorithm is based on:
Robert G. Burger and R. Kent Dybvig
Printing Floating Point Numbers Quickly and Accurately
ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation
June 1996.
http://www.cs.indiana.edu/~dyb/pubs/FP-Printing-PLDI96.pdf

Why does adding two decimals in Javascript produce a wrong result? [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
Is JavaScript’s Math broken?
Why does JS screw up this simple math?
console.log(.1 + .2) // 0.3000000000000004
console.log(.3 + .6) // 0.8999999999999999
The first example is greater than the correct result, while the second is less. ???!! How do you fix this? Do you have to always convert decimals into integers before performing operations? Do I only have to worry about adding (* and / don't appear to have the same problem in my tests)?
I've looked in a lot of places for answers. Some tutorials (like shopping cart forms) pretend the problem doesn't exist and just add values together. Gurus provide complex routines for various math functions or mention JS "does a poor job" in passing, but I have yet to see an explanation.
It's not a JS problem but a more general computer one. Floating number can't store properly all decimal numbers, because they store stuff in binary
For example:
0.5 is store as b0.1
but 0.1 = 1/10 so it's 1/16 + (1/10-1/16) = 1/16 + 0.0375
0.0375 = 1/32 + (0.0375-1/32) = 1/32 + 00625 ... etc
so in binary 0.1 is 0.00011...
but that's endless.
Except the computer has to stop at some point. So if in our example we stop at 0.00011 we have 0.09375 instead of 0.1.
Anyway the point is, that doesn't depend on the language but on the computer. What depends on the language is how you display numbers. Usually, the language rounds numbers to an acceptable representation. Apparently JS doesn't.
So what you have to do (the number in memory is accurate enough) is just to tell somehow to JS to round "nicely" number when converting them to text.
You may try the sprintf function which give you a fine control of how to display a number.
From The Floating-Point Guide:
Why don’t my numbers, like 0.1 + 0.2
add up to a nice round 0.3, and
instead I get a weird result like
0.30000000000000004?
Because internally, computers use a
format (binary floating-point) that
cannot accurately represent a number
like 0.1, 0.2 or 0.3 at all.
When the code is compiled or
interpreted, your “0.1” is already
rounded to the nearest number in that
format, which results in a small
rounding error even before the
calculation happens.
The site has detailed explanations as well as information on how to fix the problem (and how to decide whether it is a problem at all in your case).
This is not a javascript only limitation, it applies to all floating point calculations. The problem is that 0.1 and 0.2 and 0.3 are not exactly representable as javascript (or C or Java etc) floats. Thus the output you are seeing is due to that inaccuracy.
In particular only certain sums of powers of two are exactly representable. 0.5 = =0.1b = 2^(-1), 0.25=0.01b=(2^-2), 0.75=0.11b = (2^-1 + 2^-2) are all OK. But 1/10 = 0.000110001100011..b can only be expressed as an infinite sum of powers of 2, which the language chops off at some point. Its this chopping that is causing these slight errors.
This is normal for all programming languages because not all decimal values can be represented exactly in binary. See What Every Computer Scientist Should Know About Floating-Point Arithmetic
It has to do with how computers handle floating numbers. You can read more about it here: http://docs.sun.com/source/806-3568/ncg_goldberg.html

Categories