This was originally a short reply to a toot by @[email protected], but it kinda grew.
Let me first quote the original toot:
Interesting difference when creating #JavaScript numbers:
Before number literals, plus and minus are unary operators (that coerce to number) and not part of the literal: https://tc39.es/ecma262/#sec-literals-numeric-literals
> +5
5
> -5
-5
> +true
1
> -true
-1
> +'12'
12
> -'12'
-12
When parsing strings, the grammar includes the plus and the minus: https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
> Number('+5')
5
> Number('-5')
-5
Indeed! Some more interesting observations:
-
When parsing strings as numbers, whitespace inbetween the sign and the number is not allowed. Can't repeat the sign or have more than one sign.
-
When parsing strings as numbers, whitespace around the number is allowed and ignored.
-
When parsing strings as numbers,
_
separators are not allowed, because reasons. -
Empty string parses to
0
! -
Number literal
012
is equal to10
in decimal (because012
is interpreted as octal). However'012'
parsed according to theStringNumericLiteral
grammar is12
(because'012'
is interpreted as decimal with a leading zero). -
Infinity
is not technically a number literal, but a global property; however when parsing strings,Infinity
is interpreted as IEEE 754 Infinity; NB JavaScript engines tend to print the value of Infinity with the same syntax coloring as numbers; however as input Infinity may look like a keyword or a variable. Code editors, IDEs, and various syntax highlighters may highlight it like a number, a variable, or a keyword. For example in VSCode a JavaScript snippet inside a markdown has Infinity colored as a keyword when editing as a text file and as a variable when looking at a preview. Point is: the treatment ofInfinity
is very inconsistent. -
NaN
is also not a number literal, but a global property; when parsing strings it happens to parse as IEEE 754 NaN, but not because theStringNumericLiteral
grammar specifies it as a literal, but because it is treated as garbage. Parsing garbage always returnsNaN
. Syntax coloring of NaN is inconsistent, similarly to Infinity, but not necessarily in the same way. E.g. the Firefox JS console prints Infinity colored like a number, but it colors NaN differently. Maybe that's right, since NaN means Not a Number after all. Chrome doesn't agree though. Maybe that's right, since NaN is an IEEE 754 number after all. Also, let's not forget that NaN !== NaN (as per IEEE 754). -
BTW this is not the Stroop test.
-
While we're at
NaN
and IEEE 754:-
NaN ** 0 === 1
is true -
There are actually 9007199254740990 different
NaN
s according to IEEE 754. There is no way to enter a specific one in JS, unless we do some bit fiddling via typed arrays. -
Since
NaN !== NaN
andNaN != NaN
, we needNumber.isNaN
to check whether a value is a NaN. There is also globalisNaN
which behaves slightly differently. Because it performs confusing coercions, itNumber.isNaN
is recommended over it. -
NaN
is also available asNumber.NaN
. There is noNumber.Infinity
however. Instead, there isNumber.POSITIVE_INFINITY
andNumber.NEGATIVE_INFINITY
.
-
-
Number.parseInt
andNumber.parseFloat
(and their global counterparts which, unlike Number.isNaN and global isNaN, behave identically) have their own grammars which are not like each other (obviously?), and also not like theStringNumericLiteral
grammar. So a JS engine implements at least 4 different grammars for numbers. They overlap, but each has its own quirks and gotchas incompatible with the others (e.g.+''
,Number('')
, andnew Number('')
interpret the empty string as0
, whereasNumber.parseInt
andNumber.parseFloat
asNaN
).parseInt
isn't technically defined in terms of a formal grammar, but a special algorithm. -
Some of the above is probably the reason why JSON doesn't have Infinity or NaN in its grammar (another reason being that Mr Douglas Crockford doesn't seem to be a fan of IEEE 754).
JSON.stringify
converts them tonull
s. Which is rather unfortunate if you happen to be dealing with these values. -
BTW, since JSON numbers have their own grammar and don't follow IEEE 754, they have unspecified precision. However JavaScript's
JSON.parse
always converts them to the native number type (IEEE 754), so big numbers lose precision. E.g. 64-bit integers produced outside of JavaScript and serialized to JSON are technically valid JSON, but JavaScript's JSON.parse will truncate them if they don't fit around 53 bits. E.g.JSON.parse('121232324132942198400')
will become121232324132942200000
. This may lead to a bad time. -
So, taking JSON.parse into account, we have at least 5 different number grammars in JavaScript.
-
Keeping track of all this is definitely easy and I totally didn't forget or mix anything up.
-
Computers are fun!