This blog post explains how JavaScript displays
numbers. It also examines when JavaScript uses exponential notation and when it
uses fixed notation.
This post is part
of a series on JavaScript numbers that currently comprises the following other
post:
·
Integers
and shift operators in JavaScript
Displaying decimal
numbers
JavaScript numbers
are internally stored in binary floating point. But they are usually displayed
in the decimal system.
Fixed notation versus exponential notation
There are two
decimal notations used by JavaScript: Fixed notation
[ "+" | "-" ] digit+ [ "." digit+ ]
and exponential
notation
[ "+" | "-" ] digit [ "." digit+ ]
"e" [ "+" | "-" ] digit+
An example of
exponential notation is -37e+ For output, there is always exactly one digit
before the point, for input you can use more than one digit. Exponential
notation is interpreted as follows: Given a number in exponential notation
significand e exponent
The value of that
number is
significand × 10exponent
Hence, -37e+2
represents the number −1
Displaying decimal numbers
The rules for
displaying numbers are can be summarized as follows:
·
Use exponential notation if there are
more than 21 digits before the decimal point. Example:
view source
print?
> 1234567890123456789012
2345678901234568e+21
> 123456789012345678901
123456789012345680000
·
Use exponential notation if number
starts with “” followed by more than five zeros. Example:
view source
print?
> 0000003
3e-7
> 000003
000003
·
Otherwise, use fixed notation.
Read on if you want
to know more about how displaying numbers is specified.
The ECMAScript 1
display algorithm in detail
Sect. 1 of the
ECMAScript 1 specification describes the algorithm for displaying a decimal
number. This section explains it. We first need to make a few preliminary
definitions.
Preliminary definitions
The mantissa of a floating point number is an integer – the significant digits plus a
sign. Leading and trailing zeros are discarded. Examples:
·
The mantissa of 34 is 12
·
The mantissa of 00045 is 45
·
The mantissa of 1000 is 1
·
The mantissa of −27 is −27
The general idea is
that you only need integers if you store a floating point number as a pair
(mantissa, exponent), to be interpreted like this:
mantissa × 10exponent
The ECMAScript
specification varies this idea by expressing a number as
mantissa × 10pointPos−digitCount
Hence, the previous
exponent is now pointPos−digitCount. digitCount denotes the “length” of the
mantissa, the number of digits that it has. Based on that definition, pointPos
works as follows.
·
pointPos = 0: point is before the
digits.
·
> 123 * Math.pow(10, 0 - 3)
·
123
·
pointPos ≥ 1: point is after the 1st
(2nd, etc.) digit. If pointPos is less than digitCount then then the point
appears “inside” the mantissa:
·
> 123 * Math.pow(10, 1 - 3)
·
23
If pointPos is the same as digitCount then the point appears after the
last digit of the mantissa.
> 123 * Math.pow(10, 3 - 3)
123
If pointPos is greater than digitCount then zeros are inserted after the
mantissa and before the point.
> 123 * Math.pow(10, 5 - 3)
12300
·
pointPos ≤ −1: one (two, etc.) zeros
appear after the point and before the mantissa.
·
> 123 * Math.pow(10, -2 - 3)
00123
·
The algorithm
Given a number
mantissa × 10pointPos−digitCount
The algorithm has
four main cases (here, the last case merges the actual algorithm’s last two
cases).
1. No decimal point: digitCount
≤ pointPos ≤ 21
Print the digits (without leading zeros), followed by pointPos−digitCount zeros.
Print the digits (without leading zeros), followed by pointPos−digitCount zeros.
2. Decimal point inside the mantissa: 0 <
pointPos ≤ 21, pointPos < digitCount
Display the pointPos first digits of the mantissa, a point and then the remaining digitCount−pointPos digits.
Display the pointPos first digits of the mantissa, a point and then the remaining digitCount−pointPos digits.
3. Decimal point comes before the
mantissa: −6 < pointPos ≤ 0
Display a 0 followed by a point, −pointPos zeros and the mantissa.
Display a 0 followed by a point, −pointPos zeros and the mantissa.
4. Exponential notation: pointPos
≤ -6 or pointPos > 21
Display the first digit of the mantissa. If there are more digits then display a point and the remaining digits. Next, display the character e and a plus or minus sign (depending on the sign of pointPos−1), followed by the absolute value of pointPos− Therefore, the result looks as follows.
Display the first digit of the mantissa. If there are more digits then display a point and the remaining digits. Next, display the character e and a plus or minus sign (depending on the sign of pointPos−1), followed by the absolute value of pointPos− Therefore, the result looks as follows.
5.
mantissa0 [ "." mantissa.digitCount ]
"e" signChar(pointPos−1) abs(pointPos−1)
"e" signChar(pointPos−1) abs(pointPos−1)
Methods for
converting numbers to string
This conversion is
usually a preliminary step for displaying a number: Convert the number to a
string that “looks” as desired and then show it somewhere.
Number.prototype.toString(radix?)
The parameter radix
indicates the base of the system in which the number is to be displayed. The
most common radices are 10 (decimal), 2 (binary) and 16 (hexadecimal).
> .toString(2)
'1111'
> 6toString(16)
'ffff'
The radix must be
at least 2 and at most Any radix greater
than 10 leads to alphabetical characters being used as digits, which explains
the maximum 36, as the latin alphabet has 26 characters.
> 12345678toString(36)
'kf12oi'
The global function
parseInt allows you to convert such notations back to a number:
> parseInt('kf12oi', 36)
1234567890
If the radix is 10,
the algorithm from Sect. 1 is used to convert the number to a string.
Number.prototype.toExponential(fractionDigits?)
This method forces
a number to be expressed in exponential notation. fractionDigits is a number
between 0 and 20 that determines how many digits should be shown after the
decimal point. If it is omitted then “include as many significand [mantissa]
digits as necessary to uniquely specify the Number” (ECMAScript 1
specification, Sect. 6). The following are a few examples.
Force more
precision when toString() would also use exponential notation. Results are
mixed, because one reaches the limits of the precision that can be achieved
when converting binary numbers to a decimal notation.
> 1234567890123456789toString()
'2345678901234568e+21'
> 1234567890123456789toExponential(20)
'23456789012345677414e+21'
Get exponential
notation when numbers are not large enough.
> 1toString()
'1234'
> 1toExponential(5)
'23400e+3'
> 1toExponential()
'234e+3'
Get exponential
notation when non-zero numbers are not small enough.
> 0toString()
'003'
> 0toExponential(4)
'0000e-3'
> 0toExponential()
'3e-3'
Number.prototype.toFixed(fractionDigits?) If the
number is greater than 1021 then this method is the same as toString(). Thus,
you are not guaranteed to not get a number in exponential notation.
> 1234567890123456789toFixed()
'2345678901234568e+21'
> 1234567890123456789toString()
'2345678901234568e+21'
Otherwise, you will get a fixed point
representation of the number, rounded to fractionDigits digits. If the
parameter is omitted, the value 0 is used.
> 00000toFixed(10)
'0000003000'
> 00000toString()
'3e-7'
Number.prototype.toPrecision(precision?) This
method prunes the mantissa to precision digits, before using a conversion
algorithm similar to toString(). If no precision is given, toString() is used
directly.
> 1toPrecision(3)
'23e+3'
> 1toPrecision(4)
'1234'
> 1toPrecision(5)
'120'
> 2toPrecision(3)
'23'
Obviously, you need the exponential notation to
display 1234 with a precision of 3 digits.
Conclusion
This post described how JavaScript numbers are
displayed. While their internal representation is binary, the default for displaying
them is decimal. The resulting loss in precision is problematic and will be the
topic of an upcoming blog post. You have also learned that JavaScript uses
fixed notation except for numbers greater than 1021 and numbers that start with
“” followed by more than 5 zeros.
It is interesting to note that you
can always append ex to a number and it will be multiplied by 10x.
> 123e3
123000
> 123e-3
No comments:
Post a Comment