JS Quirks: stringified numbers

#JavaScript #quirks

Table of Contents

When I was working on Node.js PR 9492, this comment

This should probably test for a wider range of values. test/parallel/test-net-internal.js has some good examples.

made me look at that file. As I was going through the test, few of the bad values were interesting. I normally test with stringified positive decimal numbers and negative decimal numbers. But I saw stringified negative octal, binary, and hexa decimal numbers.

1const bad = [-1, 'a', {}, [], false, true, 0xFFFF + 1, Infinity,
2             -Infinity, NaN, undefined, null, '', ' ', 1.1, '0x',
3             '-0x1', '-0o1', '-0b1', '0o', '0b'];

I got curious as I have never used them before, I just wanted to see their corresponding negative values. So I wrote a program like this

1[-0x1, '-0x1', -0o1, '-0o1', -0b1, '-0b1'].forEach(item => console.log(item, +item));

and I was expecting to see the result

1-1 -1
2-0x1 -1
3-1 -1
4-0o1 -1
5-1 -1
6-0b1 -1

but all I got was

1-1 -1
2-0x1 NaN
3-1 -1
4-0o1 NaN
5-1 -1
6-0b1 NaN

The unary - operator simply negates the magnitude of the numbers. The stringified numbers were not processed in the same way as their number counterparts. So I looked at the ECMAScript specification's ToNumber Applied to the String Type section (which is actually responsible for converting strings to numbers).

 1StrNumericLiteral :::
 2    StrDecimalLiteral
 3    BinaryIntegerLiteral
 4    OctalIntegerLiteral
 5    HexIntegerLiteral
 6...
 7...
 8StrDecimalLiteral :::
 9    StrUnsignedDecimalLiteral
10    + StrUnsignedDecimalLiteral
11    - StrUnsignedDecimalLiteral

Only the StrDecimalLiteral production allows signed numbers. If we look at the definition of others in the Numeric Literals section,

 1BinaryIntegerLiteral ::
 2    0b BinaryDigits
 3    0B BinaryDigits
 4
 5BinaryDigits ::
 6    BinaryDigit
 7    BinaryDigits BinaryDigit
 8
 9BinaryDigit :: one of
10    0 1
11
12OctalIntegerLiteral ::
13    0o OctalDigits
14    0O OctalDigits
15
16OctalDigits ::
17    OctalDigit
18    OctalDigits OctalDigit
19
20OctalDigit :: one of
21    0 1 2 3 4 5 6 7
22
23HexIntegerLiteral ::
24    0x HexDigits
25    0X HexDigits
26
27HexDigits ::
28    HexDigit
29    HexDigits HexDigit
30
31HexDigit :: one of
32    0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F

So, as per the specification, only the decimal numbers can have signs in the stringified number form. That is why the others are not considered as numbers.