Can I ever run into any floating number precision errors if I don't perform any arithmetic operations on the floats? The only operations I do with numbers in my program are limited to the following:
Getting numbers as strings from a web service and converting them to floats using parseFloat()
Comparing resulting floats using <= < == > >=
Example:
const input = ['1000.69', '1001.04' /*, ... */]
const x = parseFloat(input[0])
const y = parseFloat(input[1])
console.log(x < y)
console.log(x > y)
console.log(x == y)
As for parseFloat() implemetation, I'm using latest Node.js.
The source of floats is prices in USD as strings, always two decimals.
As long as the source of your floats is reliable, your checks are safe, yes.
I'd still round them to an acceptable decimal number after the parsing, just to be 100% safe.
As the MDN docs show in one of their examples
// these all return 3.14
parseFloat(3.14);
parseFloat('3.14');
parseFloat(' 3.14 ');
parseFloat('314e-2');
parseFloat('0.0314E+2');
parseFloat('3.14some non-digit characters');
parseFloat({ toString: function() { return "3.14" } });
//and of course
parseFloat('3.140000000') === 3.14
The parseFloat operation converts a string into it's number value. The spec says:
In this specification, the phrase “the Number value for x” where x represents an exact real mathematical quantity (which might even be an irrational number such as π) means a Number value chosen in the following manner. Consider the set of all finite values of the Number type, with -0 removed and with two additional values added to it that are not representable in the Number type, namely 2ℝ1024ℝ (which is +1ℝ × 2ℝ53ℝ × 2ℝ971ℝ) and -2ℝ1024ℝ (which is -1ℝ × 2ℝ53ℝ × 2ℝ971ℝ). Choose the member of this set that is closest in value to x.
That reads as if two same strings are always converted to the same closest number. Except for NaN, two same numbers are equal.
6.1.6.1.13 Number::equal ( x, y )
If x is NaN, return false.
If y is NaN, return false.
If x is the same Number value as y, return true.
If x is +0 and y is -0, return true.
If x is -0 and y is +0, return true.
Return false.
emphasis mine
Related
Reading through the ECMAScript 5.1 specification, +0 and -0 are distinguished.
Why then does +0 === -0 evaluate to true?
JavaScript uses IEEE 754 standard to represent numbers. From Wikipedia:
Signed zero is zero with an associated sign. In ordinary arithmetic, −0 = +0 = 0. However, in computing, some number representations allow for the existence of two zeros, often denoted by −0 (negative zero) and +0 (positive zero). This occurs in some signed number representations for integers, and in most floating point number representations. The number 0 is usually encoded as +0, but can be represented by either +0 or −0.
The IEEE 754 standard for floating point arithmetic (presently used by most computers and programming languages that support floating point numbers) requires both +0 and −0. The zeroes can be considered as a variant of the extended real number line such that 1/−0 = −∞ and 1/+0 = +∞, division by zero is only undefined for ±0/±0 and ±∞/±∞.
The article contains further information about the different representations.
So this is the reason why, technically, both zeros have to be distinguished.
However, +0 === -0 evaluates to true. Why is that (...) ?
This behaviour is explicitly defined in section 11.9.6, the Strict Equality Comparison Algorithm (emphasis partly mine):
The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:
(...)
If Type(x) is Number, then
If x is NaN, return false.
If y is NaN, return false.
If x is the same Number value as y, return true.
If x is +0 and y is −0, return true.
If x is −0 and y is +0, return true.
Return false.
(...)
(The same holds for +0 == -0 btw.)
It seems logically to treat +0 and -0 as equal. Otherwise we would have to take this into account in our code and I, personally, don't want to do that ;)
Note:
ES2015 introduces a new comparison method, Object.is. Object.is explicitly distinguishes between -0 and +0:
Object.is(-0, +0); // false
I'll add this as an answer because I overlooked #user113716's comment.
You can test for -0 by doing this:
function isMinusZero(value) {
return 1/value === -Infinity;
}
isMinusZero(0); // false
isMinusZero(-0); // true
I just came across an example where +0 and -0 behave very differently indeed:
Math.atan2(0, 0); //returns 0
Math.atan2(0, -0); //returns Pi
Be careful: even when using Math.round on a negative number like -0.0001, it will actually be -0 and can screw up some subsequent calculations as shown above.
Quick and dirty way to fix this is to do smth like:
if (x==0) x=0;
or just:
x+=0;
This converts the number to +0 in case it was -0.
2021's answer
Are +0 and -0 the same?
Short answer: Depending on what comparison operator you use.
Long answer:
Basically, We've had 4 comparison types until now:
‘loose’ equality
console.log(+0 == -0); // true
‘strict’ equality
console.log(+0 === -0); // true
‘Same-value’ equality (ES2015's Object.is)
console.log(Object.is(+0, -0)); // false
‘Same-value-zero’ equality (ES2016)
console.log([+0].includes(-0)); // true
As a result, just Object.is(+0, -0) makes difference with the other ones.
const x = +0, y = -0; // true -> using ‘loose’ equality
console.log(x === y); // true -> using ‘strict’ equality
console.log([x].indexOf(y)); // 0 (true) -> using ‘strict’ equality
console.log(Object.is(x, y)); // false -> using ‘Same-value’ equality
console.log([x].includes(y)); // true -> using ‘Same-value-zero’ equality
In the IEEE 754 standard used to represent the Number type in JavaScript, the sign is represented by a bit (a 1 indicates a negative number).
As a result, there exists both a negative and a positive value for each representable number, including 0.
This is why both -0 and +0 exist.
Answering the original title Are +0 and -0 the same?:
brainslugs83 (in comments of answer by Spudley) pointed out an important case in which +0 and -0 in JS are not the same - implemented as function:
var sign = function(x) {
return 1 / x === 1 / Math.abs(x);
}
This will, other than the standard Math.sign return the correct sign of +0 and -0.
We can use Object.is to distinguish +0 and -0, and one more thing, NaN==NaN.
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
I'd blame it on the Strict Equality Comparison method ( '===' ).
Look at section 4d
see 7.2.13 Strict Equality Comparison on the specification
If you need sign function that supports -0 and +0:
var sign = x => 1/x > 0 ? +1 : -1;
It acts as Math.sign, except that sign(0) returns 1 and sign(-0) returns -1.
There are two possible values (bit representations) for 0. This is not unique. Especially in floating point numbers this can occur. That is because floating point numbers are actually stored as a kind of formula.
Integers can be stored in separate ways too. You can have a numeric value with an additional sign-bit, so in a 16 bit space, you can store a 15 bit integer value and a sign-bit. In this representation, the value 1000 (hex) and 0000 both are 0, but one of them is +0 and the other is -0.
This could be avoided by subtracting 1 from the integer value so it ranged from -1 to -2^16, but this would be inconvenient.
A more common approach is to store integers in 'two complements', but apparently ECMAscript has chosen not to. In this method numbers range from 0000 to 7FFF positive. Negative numbers start at FFFF (-1) to 8000.
Of course, the same rules apply to larger integers too, but I don't want my F to wear out. ;)
Wikipedia has a good article to explain this phenomenon: http://en.wikipedia.org/wiki/Signed_zero
In brief, it both +0 and -0 are defined in the IEEE floating point specifications. Both of them are technically distinct from 0 without a sign, which is an integer, but in practice they all evaluate to zero, so the distinction can be ignored for all practical purposes.
I'm testing toFixed() method of javascript. The result is seen as below.
(49.175).toFixed(2) => "49.17"
(49.775).toFixed(2) => "49.77"
(49.185).toFixed(2) => "49.19"
(49.785).toFixed(2) => "49.78"
(49.1175).toFixed(3) => "49.117"
(49.1775).toFixed(3) => "49.178"
(49.1185).toFixed(3) => "49.118"
(49.1785).toFixed(3) => "49.178"
I made this test at chrome browser, and I'm surprised with the result. I couldn't catch the logic. It doesn't fit neither 'round away from zero' nor 'round to even'.
What is the rule behind of 'toFixed()' function ?
About toFixed
Returns a String containing this Number value represented in decimal fixed-point notation with fractionDigits digits after the decimal point. If fractionDigits is undefined, 0 is assumed. Specifically, perform the following steps:
Algorithm Number.prototype.toFixed (fractionDigits): https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4.5
The length property of the toFixed method is 1.
If the toFixed method is called with more than one argument, then the behaviour is undefined (see clause 15).
An implementation is permitted to extend the behaviour of toFixed for values of fractionDigits less than 0 or greater than 20. In this case toFixed would not necessarily throw RangeError for such values.
NOTE The output of toFixed may be more precise than toString for some values because toString only prints enough significant digits to distinguish the number from adjacent number values.
JS Work Around
function fix(n, p) {
return (+(Math.round(+(n + 'e' + p)) + 'e' + -p)).toFixed(p);
}
let exampleA = fix(49.1175, 3);
let exampleB = fix(49.1775, 3);
let exampleC = fix(49.775, 2);
const random = Math.random();
console.log(exampleA);
console.log(exampleB);
console.log(exampleC);
console.log('Before:', random, 'After Custom =>', fix(random, 3), 'Default:', random.toFixed(3));
// 49.118
// 49.178
// 49.78
Precision Needed
I suggest just simply porting set precision from C++ to a Node.JS Module.
You could simply rig up and use a child_process also in Node.JS to call a C++ program with an argument, and have the C++ run a function to convert the value and output to the console.
The issue is, that the numbers you entered do not exist! On scanning, they are (binary) rounded to the nearest possible/existing number. toPrecision(18) shows the numbers after scanning more exact:
(49.175).toPrecision(18); // "49.1749999999999972" => "49.17"
(49.775).toPrecision(18); // "49.7749999999999986" => "49.77"
(49.185).toPrecision(18); // "49.1850000000000023" => "49.19"
(49.785).toPrecision(18); // "49.7849999999999966" => "49.78"
So the number is rounded 2 times: First on scanning, and then by toFixed().
From the MDN:
toFixed() returns a string representation of numObj that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary, and the fractional part is padded with zeros if necessary so that it has the specified length. If numObj is greater or equal to 1e+21, this method simply calls Number.prototype.toString() and returns a string in exponential notation.
And later you can read:
WARNING: Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results such as 0.1 + 0.2 === 0.3 returning false.
The above warning in conjuntion with the round logic (maybe arithmetical operations on the number) will explain the different behaviours you are experimenting in the rounding procedure (you can read it here).
/*
how can i return the full value not exponential?
*/
x = 11111111111111111111111112;
y = 23233333333333333333333333;
console.log(x+y);
//how can i return the full value not exponential?
All the positive and negative integers whose magnitude is no greater than 253 are representable in the Number type (the integer 0 has two representations, +0 and −0). That means the valid range for Number is +/- 9007199254740991.
Anything bigger than that range are handled as floating point, in which case it is really difficult to avoid exponent.
let x = 11111111111111111111111112;
let y = 23233333333333333333333333;
console.log(parseFloat(x) + parseFloat(y))
console.log(x+y);
If you want to handle big integers, you can take the help of some library:
BigInteger
How do works Math.fround() function.
The Math.fround() function returns the nearest float representation of a number.
But when it is passed the float number Math.fround(1.5) it returns the same(1.5) value.
But when it is passed the float number Math.fround(1.55) it returns a different value 1.5499999523162841. why and How?
I am confused about how Math.fround() works.
How is it different from the Math.round() function?
Also let me know why it's not supported in Internet Explorer.
To understand how this function works you actually have to know the following topics:
JavaScript’s Number type in details
The simple math behind decimal-binary conversion algorithms
How to round binary numbers
The mechanics behind exponent bias in floating point
The ECMA script specifies the following algorithm for the conversion:
When Math.fround is called with argument x, the following steps are
taken:
If x is NaN, return NaN.
If x is one of +0, -0, +∞, -∞, return x.
Let x32 be the result of converting x to a value in IEEE 754-2008 binary32
format using roundTiesToEven.
Let x64 be the result of converting x32
to a value in IEEE 754-2008 binary64 format.
Return the ECMAScript
Number value corresponding to x64.
So, let's do that for 1.5 and 1.55.
Math.fround(1.5)
1) Represent in 64bit float
0 01111111111 1000000000000000000000000000000000000000000000000000
2) Represent in the scientific notation
1.1000000000000000000000000000000000000000000000000000 x 2^0
3) Round to 23 bit mantissa
1.10000000000000000000000
4) Convert to decimal:
1.5
Math.fround(1.55)
1) Represent in 64bit float
0 01111111111 1000110011001100110011001100110011001100110011001101
2) Represent in the scientific notation
1.1000110011001100110011001100110011001100110011001101 x 2^0
3) Round to 23 bit mantissa
1.10001100110011001100110
4) Convert to decimal:
1.5499999523162841
I got these below things which is I got from some of the documents. Kindly share your answers if you have more details.
I have found something about Math.fround ( x ) from ECMAScript® 2015 Language Specification
When Math.fround() is called with argument x the following steps are taken:
If x is NaN, return NaN.
If x is one of +0, −0, +∞, −∞, return x.
Let x32 be the result of converting x to a value in IEEE 754-2008 binary32 format using roundTiesToEven.
Let x64 be the result of converting x32 to a value in IEEE 754-2008 binary64 format.
Return the ECMAScript Number value corresponding to x64.
This can be emulated with the following function, if Float32Array are supported:
Math.fround = Math.fround || (function (array) {
return function(x) {
return array[0] = x, array[0];
};
})(Float32Array(1));
** Kindly share more answers for learning more details
This question already has answers here:
What is the rationale for all comparisons returning false for IEEE754 NaN values?
(12 answers)
Closed 10 years ago.
Why are these two different?
var x = NaN; //e.g. Number("e");
alert(isNaN(x)); //true (good)
alert(x == NaN); //false (bad)
Nothing is equal to NaN. Any comparison will always be false.
In both the strict and abstract comparison algorithms, if the types are the same, and either operand is NaN, the result will be false.
If Type(x) is Number, then
If x is NaN, return false.
If y is NaN, return false.
In the abstract algorithm, if the types are different, and a NaN is one of the operands, then the other operand will ultimately be coerced to a number, and will bring us back to the scenario above.
The equality and inequality predicates are non-signaling so x = x returning false can be used to test if x is a quiet NaN.
Source
This is the rule defined in IEEE 754 so full compliance with the specification requires this behavior.
The following operations return NaN
The divisions 0/0, ∞/∞, ∞/−∞, −∞/∞, and −∞/−∞
The multiplications 0×∞ and 0×−∞
The power 1^∞
The additions ∞ + (−∞), (−∞) + ∞ and equivalent subtractions.
Real operations with complex results:
The square root of a negative number
The logarithm of a negative number
The tangent of an odd multiple of 90 degrees (or π/2 radians)
The inverse sine or cosine of a number which is less than −1 or greater than +1.
The following operations return values for numeric operations. Hence typeof Nan is a number. NaN is an undefined number in mathematical terms. ∞ + (-∞) is not equal to ∞ + (-∞). But we get that NaN is typeof number because it results from a numeric operation.
From wiki: