Odd behavior with ParseFloat when my string is too long - javascript

I was making a calculator (something like excel in javascript) and I have found a strange behavior with ParseFloat.
parseFloat(999999999999999) //999999999999999
parseFloat(9999999999999999) //10000000000000000
parseFloat(9999999999999899) //9999999999999900
Is there a limit with parseFloat function in javascript? Following ECMA Standard there is no issue about this.

Float is not an endless container. Consider this example:
console.log(0.1 + 0.2 == 0.3) // Prints... FALSE!
Or, another case:
console.log(99999999999999999999999999999999999) // Prints 1e+35
...while 1e+35 is just 1 with 35 zeroes. Original number (9999...) is so large and precise that JS starts cutting lower digits to store at least something - the source is too big to save in float.
This actually happens because of internal float conversions made by JavaScript engine and the philosophy of float type is that higher digits are more important that lower.
Your case is somewhat similar. This is because floating point type accuracy depends on its value length. So, If your value is too big or too small, you will lose precision for lower digits.
Thus you should never trust float and never compare it with other values using '==' of '===' - it may be anything.

Related

Keep trailing or leading zeroes on number

Is it possible to keep trailing or leading zeroes on a number in javascript, without using e.g. a string instead?
const leading = 003; // literal, leading
const trailing = 0.10; // literal, trailing
const parsed = parseFloat('0.100'); // parsed or somehow converted
console.log(leading, trailing, parsed); // desired: 003 0.10 0.100
This question has been regularly asked (and still is), yet I don't have a place I'd feel comfortable linking to (did i miss it?).
Fully analogously would be keeping any other aspect of the representation a number literal was entered as, although asked nowhere near as often:
console.log(0x10); // 16 instead of potentially desired 0x10
console.log(1e1); // 10 instead of potentially desired 1e1
For disambiguation, this is not about the following topics, for some of which I'll add links, as they might be of interest as well:
Padding to a set amount of digits, formatting to some specific string representation, e.g. How can i pad a value with leading zeroes?, How to output numbers with leading zeros in JavaScript?, How to add a trailing zero to a price
Why a certain string representation will be produced for some number by default, e.g. How does JavaScript determine the number of digits to produce when formatting floating-point values?
Floating point precision/accuracy problems, e.g. console.log(0.1 + 0.2) producing 0.30000000000000004, see Is floating point math broken?, and How to deal with floating point number precision in JavaScript?
No. A number stores no information about the representation it was entered as, or parsed from. It only relates to its mathematical value. Perhaps reconsider using a string after all.
If i had to guess, it would be that much of the confusion comes from the thought, that numbers, and their textual representations would either be the same thing, or at least tightly coupled, with some kind of bidirectional binding between them. This is not the case.
The representations like 0.1 and 0.10, which you enter in code, are only used to generate a number. They are convenient names, for what you intend to produce, not the resulting value. In this case, they are names for the same number. It has a lot of other aliases, like 0.100, 1e-1, or 10e-2. In the actual value, there is no contained information, about what or where it came from. The conversion is a one-way street.
When displaying a number as text, by default (Number.prototype.toString), javascript uses an algorithm to construct one of the possible representations from a number. This can only use what's available, the number value, also meaning it will produce the same results for two same numbers. This implies, that 0.1 and 0.10 will produce the same result.
Concerning the number1 value, javascript uses IEEE754-2019 float642. When source code is being evaluated3, and a number literal is encountered, the engine will convert the mathematical value the literal represents to a 64bit value, according to IEEE754-2019. This means any information about the original representation in code is lost4.
There is another problem, which is somewhat unrelated to the main topic. Javascript used to have an octal notation, with a prefix of "0". This means, that 003 is being parsed as an octal, and would throw in strict-mode. Similarly, 010 === 8 (or an error in strict-mode), see Why JavaScript treats a number as octal if it has a leading zero
In conclusion, when trying to keep information about some representation for a number (including leading or trailing zeroes, whether it was written as decimal, hexadecimal, and so on), a number is not a good choice. For how to achieve some specific representation other than the default, which doesn't need access to the originally entered text (e.g. pad to some amount of digits), there are many other questions/articles, some of which were already linked.
[1]: Javascript also has BigInt, but while it uses a different format, the reasoning is completely analogous.
[2]: This is a simplification. Engines are allowed to use other formats internally (and do, e.g. to save space/time), as long as they are guaranteed to behave like an IEEE754-2019 float64 in any regard, when observed from javascript.
[3]: E.g. V8 would convert to bytecode earlier than evaluation, already exchanging the literal. The only relevant thing is, that the information is lost, before we could do anything with it.
[4]: Javascript gives the ability to operate on code itself (e.g. Function.prototype.toString), which i will not discuss here much. Parsing the code yourself, and storing the representation, is an option, but has nothing to do with how number works (you would be operating on code, a string). Also, i don't immediately see any sane reason to do so, over alternatives.

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.

Math inaccuracy in Javascript: safe to use JS for important stuff?

I was bored, so I started fidlling around in the console, and stumbled onto this (ignore the syntax error):
Some variable "test" has a value, which I multiply by 10K, it suddenly changes into different number (you could call it a rounding error, but that depends on how much accuracy you need). I then multiply that number by 10, and it changes back/again.
That raises a few questions for me:
How in accurate is Javascript? Has this been determined? I.e. a number that can be taken into account?
Is there a way to fix this? I.e. to do math in Javascript with complete accuracy (within the limitations of its datatype).
Should the changed number after the second operation be interpreted as 'changing back to the original number' or 'changing again, because of the inaccuracy'?
I'm not sure whether this should be a separate question, but I was actually trying to round numbers to a certain amount after the decimal point. I've researched it a bit, and have found two methods:
> Method A
function roundNumber(number, digits) {
var multiple = Math.pow(10, digits);
return Math.floor(number * multiple) / multiple;
}
> Method B
function roundNumber(number, digits) {
return Number(number.toFixed(digits));
}
Intuitively I like method B more (looks more efficient), but I don't know what going on behind the scenes so I can't really judge. Anyone have an idea on that? Or a way to benchmark this? And why is there no native round_to_this_many_decimals function? (one that returns an integer, not a string)
How in accurate is Javascript?
Javascript uses standard double precision floating point numbers, so the precision limitations are the same as for any other language that uses them, which is most languages. It's the native format used by the processor to handle floating point numbers.
Is there a way to fix this? I.e. to do math in Javascript with complete accuracy (within the limitations of its datatype).
No. The precision limitations lies in the way that the number is stored. Floating point numbers doesn't have complete accuracy, so no matter how you do the calculations you can't achieve absolute accuracy as the result goes back into a floating point number.
If you want complete accuracy then you need to use a different data type.
Should the changed number after the second operation be interpreted as
'changing back to the original number' or 'changing again, because of
the inaccuracy'?
It's changing again.
When a number is converted to text to be displayed, it's rounded to a certain number of digits. The numbers that look like they are exact aren't, it's just that the limitations in precision doesn't show up.
When the number "changes back" it's just because the rounding again hides the limitations in the precision. Each calculation adds or subtracts a small inaccuracy in the number, and sometimes it just happens to take the number closer to the number that you had originally. Eventhough it looks like it's more accurate, it's actually less accurate as each calculation adds a bit of uncertainty.
Internally, JavaScript uses 64-bit IEEE 754 floating-point numbers, which are a widely used standard and usually guarantee about 16 digits of accuracy. The error you witnessesed was on the 17th significant digit of the number and was reeeally tiny.
Is there a way to [...] do math in Javascript with complete accuracy (within the limitations of its datatype).
I would say that JavaScript's math is completely accurate within the limitations of its datatype. The error you witnessed was outside of those limitations.
Are you working with calculations that require a higher degree of precision than that?
Should the changed number after the second operation be interpreted as 'changing back to the original number' or 'changing again, because of the inaccuracy'?
The number never really became more or less accurate than the original value. It was only when the value was converted into a decimal value that a rounding error became apparent. But this was not a case of the value "changing back" to an accurate number. The rounding error was just too small to display.
And why is there no native round_to_this_many_decimals function? (one that returns an integer, not a string)
"Why is the language this way" questions are not considered very productive here, but it is easy to get around this limitation (assuming you mean numbers and not integers). This answer has 337 upvotes: +numb.toFixed(digits);, but note that if you try to display a number produced with that expression, there's no guarantee that it will actually display with only six digits. That's probably one of the reasons why JavaScript's "round to N places" function produces a string and not a number.
I came across the same few times and with further research I was able solve the little issues by using the library below
Math.js Library
Sample
import {
atan2, chain, derivative, e, evaluate, log, pi, pow, round, sqrt
} from 'mathjs'
// functions and constants
round(e, 3) // 2.718
atan2(3, -3) / pi // 0.75
log(10000, 10) // 4
sqrt(-4) // 2i
pow([[-1, 2], [3, 1]], 2) // [[7, 0], [0, 7]]
derivative('x^2 + x', 'x') // 2 * x + 1
// expressions
evaluate('12 / (2.3 + 0.7)') // 4
evaluate('12.7 cm to inch') // 5 inch
evaluate('sin(45 deg) ^ 2') // 0.5
evaluate('9 / 3 + 2i') // 3 + 2i
evaluate('det([-1, 2; 3, 1])') // -7
// chaining
chain(3)
.add(4)
.multiply(2)
.done() // 14

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

Categories