UsefulJS.Number

The Number module provides a Format class for number formatting and parsing that uses an identical API to Intl.NumberFormat:

var n = 10000.011,
    opts = { maximumFractionDigits : 2 },
    fmt = new UsefulJS.Number.Format("fr", opts),
    numberStr = fmt.format(n);                // "10 000,01"

The values provided by Intl.NumberFormat are strictly one way: there is no way to read them back. UsefulJS.Number.Format objects can take localized number strings and turn them back into numbers:

n = fmt.parse("10 000,01");                   // 10000.01

UsefulJS.Number.Format offers the same styles as Intl.NumberFormat - decimal, percent and currency - and adds two more - scientific and engineering - since people who deal routinely with very large or very small numbers tend not to write them in longhand:

var n = 9.9999e13,
    opts = { style : "scientific", maximumSignificantDigits : 5 },
    fmt = new UsefulJS.Number.Format("en", opts),
    numberStr = fmt.format(n);                // "9.9999 × 10¹³"

Values formatted in scientific notation can be read back:

var opts = { style : "scientific" },
    parser = new UsefulJS.Number.Format("en", opts),
    n = parser.parse("9.9999 × 10¹³");       // 9.9999e13

UsefulJS.Number.Format offers another option for displaying large or small numbers: scaled values (with optional units):

var fmt = new UsefulJS.Number.Format("en", { scale : "auto", unit : "tonne" });
// Tactical nuke
var explosiveForce = fmt.format(20000);      // "20 kt"
// Strategic nuke
explosiveForce = fmt.format(10000000);       // "10 Mt"

For an international technical audience, UsefulJS.Number.Format exposes an SI option which ignores most locale settings and formats vales according to SI (Système international) rules. This may be combined with other options and styles to produce standardized, locale-neutral output:

// hi locale normally uses Devangaric digits
var fmt = new UsefulJS.Number.Format("hi", { SI : true, scale : "auto", unit : "metre second-1" }),
    c_in_vacuo = fmt.format(299792458);     // "299 792.458 km·s⁻¹"
// For diehard cgs enthusiasts
fmt = new UsefulJS.Number.Format("hi", 
    { SI : true, scale : "c", unit : "metre second-1", style : "scientific", maximumSignificantDigits : 9 });
c_in_vacuo = fmt.format(299792458);         // "2.99792458 × 10¹⁰ cm·s⁻¹"

It won't be replacing TEX in the scientist's armoury, but this should be perfectly acceptable output for a scientific application.

These formats are parseable too:

// Units and scale are detected automatically on parsing
var parser = new UsefulJS.Number.Format("hi"),
    n = parser.parse("299 792.458 km·s⁻¹"); // 299792458

Rounding

Algorithms

Formatting floating point numbers for display inevitably involves rounding them. UsefulJS.Number offers three rounding algorithms to determine how ties are settled when the least significant digit in the rounded output could go either way.

UsefulJS.Number.ROUND_HALF_TO_PLUS_INFINITY

This is the rounding you will have learned in school: round half up; this algorithm also rounds negative numbers up to become less negative: 0.25 to one decimal place is rounded up to 0.3 while -0.25 is rounded up to -0.2. This algorithm introduces positive bias since the average of a collection of rounded numbers will be slightly more than the average of the unrounded ones. This is the behaviour of Math.round.

UsefulJS.Number.ROUND_HALF_AWAY_FROM_ZERO

This is rounding half up if the number is positive but down if the number is negative. 0.25 to one decimal place is rounded up to 0.3 while -0.25 is rounded down to -0.3. This algorithm produces unbiased output only when the unrounded values are distributed evenly around 0. This is the behaviour of Number.toFixed.

UsefulJS.Number.ROUND_HALF_TO_EVEN

Half is rounded up when the least significant digit in the rounded value is even, otherwise it is rounded down. 0.25 to one decimal place is rounded down to 0.2 while -0.25 is rounded up to -0.2. 0.35, on the other hand, is rounded up to 0.4 while -0.35 is rounded down to -0.4. This algorithm produces unbiased output and is the default rounding algorithm for the IEEE Standard for Floating Point Arithmetic.

Controlling rounding

You select the rounding algorithm to be used for formatting through the UsefulJS.Number.rounding property which should be set to one of the three values above. The default algorithm is ROUND_HALF_TO_EVEN.

Changing the rounding algorithm also affects the formatting of %d and %f fields by UsefulJS.String.sprintf.

Floating point errors

The rounding algorithms detect and compensate for rounding errors in the floating point hardware. Running the following code will produce surprising output:

(1.015).toFixed(2);  // "1.01"

The reason, presumably, is hardware floating point error:

1.015 * 100;         // 101.49999999999999

Compare with the UsefulJS drop-in replacement for toFixed:

(1.015)._toFixed(2); // "1.02"

Do not confuse floating point error with choice of rounding algorithm:

(1.045).toFixed(2);  // 1.04: WRONG
(1.045)._toFixed(2); // 1.04: ROUND_HALF_TO_EVEN
UsefulJS.Number.rounding = UsefulJS.Number.ROUND_HALF_AWAY_FROM_ZERO;
(1.045)._toFixed(2); // 1.05

Formatting numbers

UsefulJS.Number.Format

Constructor for the class.

Syntax
new UsefulJS.Number.Format([requestedLocales[, options]])
Parameters

Locales

The requestedLocales parameter is specified either as a single BCP-47 locale tag ("en" or "en-GB") or an array of such values indicating the preferred locale and one or more fallbacks. For example, ["quz-PE", "es-PE", "es"] would result in the "es-PE" locale being selected for number formatting since Quechua is not a supported language whereas Spanish is and there is a locale object for Spanish (Peru).

The requestedLocales parameter is optional. You can set UsefulJS.Locale.current to control the default locale.

Unicode extensions

Together with selecting the locale itself, you may specifiy Unicode extensions to control the numbering system used. "-u-" is used to specify an extension and then "nu-<N>" for the numbering system. For example, to do number formatting in Hindi using Latin digits rather than the locale default, you would specify "hi-u-nu-latn". Latin ("latn") digits are the default for most locales.

Numbering systems

You may specify the following values for the numbering system: "arab", "arabext", "bali", "beng", "deva", "fullwide", "gujr", "guru", "hanidec", "khmr", "knda", "laoo", "latn", "limb", "mlym", "mong", "mymr", "orya", "tamldec", "telu", "thai" and "tibt". Of these "arab" is the default numbering system used in Arabic locales, "arabext" is the default for "fa" (Iran), "deva" is the default for "hi" (Hindi) and "beng" is the default for "bn" (Bengali) and "bn-BD" (Bangladesh).

Options

The options hash passed to the constructor may contain combinations of various named values that control the formatting of numeric values. The options and their permissible values are detailed below (options and values marked with '*' are specific to UsefulJS):

Option name Values Default Notes
minimumSignificantDigits 1 - 21 1 The fractional part of the number string will be right-padded with zeroes to make up the required number of significant digits.
maximumSignificantDigits 1 - 21 minimumSignificantDigits Insignificant digits in the number string will be replaced with zeroes. If a fractional part is not required, it won't be emitted
minimumFractionDigits 1 - 21 0 The fractional part of the number string will be right-padded with zeroes to make up the required number of fraction digits.
maximumFractionDigits 1 - 21 max(minimumFractionDigits, 3) The fractional part of the number string will be truncated. Trailing zeroes will be suppressed, possibly removing the fractional part altogether. For currency formatting, the maximum number of fractional digits is a property of the currency itself (usually 2 but sometimes 0 or 3) and should only be overridden when your application requires it.
style decimal, percent, currency, *scientific, *engineering decimal The engineering style is the same as scientific except that the exponent will always be in multiples of three.
currency The ISO 4217 currency code (found here) - When the style is "currency", not specifying a currency option causes a TypeError to be raised. This follows the practice of the Intl API.
currencyDisplay code, symbol, name symbol The "name" value is treated the same as "code" (I am not going to localize the names of around a hundred currencies into fifty languages to satisfy a questionable option value!).
useGrouping true, false true Determines whether group separators (such as ',') are inserted between groups of digits
*SI true, false false Whether numbers are formatted according to SI rules: numbering system is "latn", grouping character is NO-BREAK SPACE (\u00A0), number grouping is [3] and the decimal separator is '.', unless the value for the locale is ','.
*keep1 true, false false When using the scientific and engineering styles, the mantissa is suppressed if it is equal to 1 unless the keep1 option is true.
*unit See below - A space separated string containing one or more SI-units (and a handful of non-SI units) together with an optional +n/-n to scale the unit. Unit values in the output string are joined with the MIDDLE DOT (\u00B7) character
*scale auto, other - Scales numbers so that the output reads more naturally. See the "Scales" table below.
*base10 true, false false When dealing with byte values makes format and parse operations use powers of ten rather than powers of two. Useful when dealing with, for example, hard disk space.

The options parameter to the constructor is optional; if not supplied the effect will be the same as { style : "decimal" }.

If creating an object to parse number strings, many of the options are not required: significant digits, fraction digits and integer digits are irrelevant as are keep1 and useGrouping. The scale value is obtained automatically as are any units (although you may want to specify a unit to ensure that the value is parsed correctly).

The internationalization API allows for an additional parameter: localeMatcher. This is ignored - the algorithm used is "lookup".

The percent style automatically scales the value up by a factor of 100:

var fmt = new UsefulJS.Number.Format("en", { style : "percent" }),
    nStr = fmt.format(0.85);    // "85%"

Units

The unit option is a space-separated string of one or more unit values with an optional +/-n scale if the magnitude of the unit is not 1. For example, to express density ("kilograms per cubic metre"), the corresponding unit string would be "kilogram metre-3" while volume ("cubic metres") would be "metre+3". When combined with a scale, the magnitude of the unit is taken into account when scaling the value. For example, here is a rather CPU-intensive way of converting from cubic metres to cubic centimetres:

var fmt = new UsefulJS.Number.Format("en", { scale : "c", unit : "metre+3" }),
    volume = fmt.format(1);     // "1,000,000 cm³"
Scaling with the magnitude of the unit only works with the first unit, meaning that you can't use this capability to scale from kilograms per cubic metre to micrograms per cubic millimetre.

Here is the complete list of units:

Name Quantity Symbol
metre Length m
kilogram Mass kg
second Time s
ampere Electric current A
kelvin Thermodynamic temperature K
mole Amount of substance mol
candela Luminous intensity cd
radian Angle rad
steradian Solid angle sr
hertz Frequency Hz
newton Force N
pascal Pressure Pa
joule Energy, work J
watt Power W
coulomb Electric charge C
volt Electrical potential difference, electromotive force V
farad Electric capacitance F
ohm Electric resistance Ω
siemens Electrical conductance S
weber Magnetic flux Wb
tesla Magnetic field strength T
henry Inductance H
celsius Temperature (relative to 273.15K) °C
lumen Luminous flux lm
lux Illuminance lx
becquerel Radioactivity (decays per second) Bq
gray Absorbed dose of radiation Gy
sievert Equivalent dose of radiation Sv
katal Catalytic activity kat
gram Mass g
minute Time min
hour Time h
day Time d
year Time y
degree Angle °
arcminute Angle
arcsecond Angle
hectare Area ha
litre Volume l
tonne Mass t
angstrom Length (10-10 m) Å
bytes Data size B
meter Length (alternate spelling) m
liter Volume (alternate spelling and preferred US symbol) L

There is nothing to stop you supplying your own units, but you need to specify the symbol explicitly:

var massOfElectron = 511000,
    fmt = new UsefulJS.Number.Format("en", { scale : "M", unit : "eV" }),
    nStr = fmt.format(massOfElectron);      // "0.511 MeV"

Since values with units are intended for technical applications, there is no attempt to localize the symbols.

Scale values

These are the scale values that you can specify:

Scale Name Multiplier (base 10) Multiplier (base 2)
Y yotta- 10²⁴ 2⁸⁰
Z zetta- 10²¹ 2⁷⁰
E exa- 10¹⁸ 2⁶⁰
P peta- 10¹⁵ 2⁵⁰
T tera- 10¹² 2⁴⁰
G giga- 10⁹ 2³⁰
M mega- 10⁶ 2²⁰
k kilo- 10³ -
K kilo- - 2¹⁰
h hecto- 10² -
da deca- 10¹ -
d deci- 10⁻¹ -
c centi- 10⁻² -
m milli- 10⁻³ -
µ / micro micro- 10⁻⁶ -
n nano- 10⁻⁹ -
p pico- 10⁻¹² -
f femto- 10⁻¹⁵ -
a atto- 10⁻¹⁸ -
z zepto- 10⁻²¹ -
y yocto- 10⁻²⁴ -
auto - - -

The base-2 column is for the bytes unit:

var fmt = new UsefulJS.Number.Format("en", { scale : "auto", unit : "bytes" }),
    eightGig = fmt.format((1 << 30) * 8);    // "8 GB"

The kilo- scale for bytes is formatted as "K" rather than "k":

var twoPointFiveK = fmt.format(2560);        // "2.5 KB"

When dealing with, for example, hard disk space, a kilobyte is 1000 bytes, rather than 1024 bytes. You can use the base10 option to control whether powers of ten rather than powers of two are used:

var fmt = new UsefulJS.Number.Format("en", { scale : "auto", unit : "bytes", base10 : true }),
    diskSize = fmt.format(2e12);    // "2 TB"

The "µ" scale may be specified as "micro" since "µ" is hard to type. Note that the character used is MICRO SIGN (\u00B5) rather than GREEK SMALL LETTER MU (\u03BC).

With some units, scaling is ignored: kelvin, celsius, radian, steradian, minute, hour, day, degree, arcminute, arcsecond, angstrom.

Units are formatted with a NO-BREAK SPACE between the quantity and the unit except for degree, arcminute and arcsecond where the unit is flush against the quantity.

The "auto" scaling algorithm attempts to end up with a quantity between 1 and 1000 without the result being silly. I've not heard of a "megagram" or "megametre", so the auto-scaling does not output "Mg" or "Mm". The "c" scale is only applied automatically when the unit is metre or litre (or, if you prefer, "meter" or "liter") and other scales that are not multiples of three are never applied automatically. The kilogram unit isn't scaled down to grams, only to milligrams and smaller, and it isn't scaled up at all. That said, the algorithm isn't infallible:

var gravitationalConstant = 6.67384e-11,
    fmt = new UsefulJS.Number.Format("en", { SI : true, scale : "auto", unit : "metre+3 kilogram-1 second-2" }),
    nStr = fmt.format(gravitationalConstant;    // "66 738 400 µm³·kg⁻¹·s⁻²"

The result is correct but rather weird.

When parsing values, an SI unit is preferred to a scale-plus-unit when there is any ambiguity:

var universeAge = 1.38e10,
    fmt = new UsefulJS.Number.Format("en", { scale : "auto", unit : "year" }),
    nStr = fmt.format(universeAge);             // "13.8 Gy"
// nStr will be parsed as though the unit were Gray rather than giga-years
var parser = new UsefulJS.Number.Format(),
    radiationDose = parser.parse("13.8 Gy");    // 13.8
// Remove ambiguity by specifying the unit in the parse options
parser = new UsefulJS.Number.Format(null, { unit : "year" });
universeAge = parser.parse("13.8 Gy");          // 1.38e10

UsefulJS.Number.Format instance methods

format

Formats a number according the configured style and options.

Syntax
format(n)
Parameters

Returns: String. The formatted number

Description

If the input cannot be coerced into a sensible number, the return value is "NaN". If the input is Number.POSITIVE_INFINITY, the return value is "∞" substituted into the appropriate positive number pattern (for example, "∞%"). If the input is Number.NEGATIVE_INFINITY, the return value is "∞" substituted into the appropriate negative number pattern.

When using scientific / engineering format in a locale that does not use Latin digits, the exponent is still formatted using Latin digits. I've not been able to find out how to do scientific notation in, say, Arabic so this is my best guess, especially as superscript digits are only available in Latin form.

The native Intl.NumberFormat objects use the same rather broken truncation algorithm as Number.toFixed when formatting using the decimal style and round-half-away-from-zero when formatting currency. UsefulJS.Number.Format objects use the rounding algorithm specified through the UsefulJS.Number.rounding property. Certain locales may specify a currency rounding algorithm.

The format method is bound to its instance, meaning that you can use a pointer to the instance method where a function pointer is expected. For example, if you have a whole bunch of numbers to be formatted, you can pass fmt.format to Array.prototype.map.

Usage

See above for lots of usage examples!

parse

Parses a string produced by a Format object back into a number.

Syntax
parse(numberStr)
Parameters

Returns: Number. The parsed value.

Description

You need to set the correct style when parsing a number string if you are not to get Number.NaN back. This is because the digits first need to be extracted from locale-specific patterns before they can be parsed. You don't need to set scale and unit because they are picked up automatically and the scaling is reversed (as it is with the percent style). If the value extracted from a pattern is "∞", the return value will be an infinity; which one depends on whether the pattern matched was a positive or negative number pattern. If the value is plain not number-like, the return will be Number.NaN.

The parse method is bound to its instance.

Usage
// Parse percent strings formatted for the tu (Turkey) locale
var parser = new UsefulJS.Number.Format("tr", { style : "percent" }),
    numberStrings = ["%\u00a085", "-%\u00a050"],
    numbers = numberStrings.map(parser.parse);  // [0.85, -0.5]

resolvedOptions

Gets an Object showing what the requested options resolved to. The locale property is useful in that it gives the actual locale being used (unlike supportedLocalesOf). See the Intl.NumberFormat documentation for more information.

Syntax
resolvedOptions()

supportedLocalesOf

Part of the internationalization API and not terribly useful. Takes an array of locale codes and returns the locales that are "supported" in the sense that they do not resolve to the default locale. For example, "ru-UK" (Ukrainian Russian) would return as "supported", even though the locale that is actually supported is "ru", i.e. Russian Russian. See the Intl documentation for more information.

Syntax
supportedLocalesOf(locales[, options])

Interoperability with Intl.NumberFormat

The UsefulJS.Number.Format API is a superset of the Intl.NumberFormat API, with a couple of additional styles and some extra options. Unlike date and time formatting, number formatting is a well-defined problem: no English speaker separates groups of digits with a space and sticks a comma between the integer and fractional parts; likewise no French speaker uses commas and dots as separators. This means that UsefulJS.Number.Format should produce the same value as a native Intl.NumberFormat implementation bar differences in how half-values are rounded. It also means that UsefulJS.Number.Format should be able to parse values produced by Intl.NumberFormat.

Additional UsefulJS.Number properties and methods

EPSILON

The smallest interval between distinct floating point numbers for a given architecture. Since JavaScript uses 64-bit floating point numbers, the value is approximately 2.22045e-16. If the value is available as a property of Number, then that property is used. Otherwise it is computed (and found to be 2.220446049250313e-16). A module fix can make this a property of Number. It is used to detect errors that come from the floating point architecture itself, such as the sum of 0.1 and 0.2 being not quite 0.3.

units, scales, byte_scales

The unit and scale values used by the Format class are also available as public properties of UsefulJS.Number.

rounding

The rounding algorithm to use. See above for the available algorithms.

Fixes

The fixes for the Number module are in the _number namespace of the fix options. All fixes are applied by default.

EPSILON / MAX_SAFE_INTEGER / MIN_SAFE_INTEGER

Adds the relevant static property to the Number class if not already present.

fixToLocaleString

Patches Number.prototype.toLocaleString to take locale and formatting options, just like Intl.NumberFormat. The output property is "toLocaleStringFixed".

intl_NumberFormat

Makes the UsefulJS.Number.Format class available through the Intl.NumberFormat namespace. This allows for completely portable, locale-aware number formatting that runs in pretty much any browser, using the native object when available.

_toFixed

Adds a method called _toFixed to Number.prototype which is intended as a drop-in replacement for toFixed. Details of the method are as follows:

Number.prototype._toFixed

Truncates the string representation of a floating point number to a specified precision, rounding the result correctly.

Syntax
<N>._toFixed([maxPrecision[, minPrecision]])
Parameters

Returns: String. The fixed-precision output.

Description

I hate Number.toFixed. Even if the implementation isn't so brain-damaged that it rounds 0.9 to 0, it still uses an algorithm that incorrectly rounds values for a wide range of inputs and gives you the number of digits after the decimal point whether you want them or not. _toFixed rounds numbers correctly, suppresses meaningless zeroes at the end of the output and allows you to override this behaviour through the minPrecision parameter:

(1).toFixed(2);      // 1.00
(1)._toFixed(2);     // 1
(1)._toFixed(2, 2);  // 1.00

Very large and very small numbers are handled:

(1.23e50).toFixed();     // "1.23e+50"
(1.23e50)._toFixed();    // "123000000000000000000000000000000000000000000000000"
(1.23e-21).toFixed(22);  // throws
(1.23e-21)._toFixed(22); // "0.0000000000000000000012"