diff --git a/README.md b/README.md index 2259cde..6f08901 100644 --- a/README.md +++ b/README.md @@ -133,9 +133,9 @@ The *symbol* can be: The *zero* (`0`) option enables zero-padding; this implicitly sets *fill* to `0` and *align* to `=`. The *width* defines the minimum field width; if not specified, then the width will be determined by the content. The *comma* (`,`) option enables the use of a group separator, such as a comma for thousands. -Depending on the *type*, the *precision* either indicates the number of digits that follow the decimal point (types `f` and `%`), or the number of significant digits (types `​`, `e`, `g`, `r`, `s` and `p`). If the precision is not specified, it defaults to 6 for all types except `​` (none), which defaults to 12. Precision is ignored for integer formats (types `b`, `o`, `d`, `x`, `X` and `c`). See [precisionFixed](#precisionFixed) and [precisionRound](#precisionRound) for help picking an appropriate precision. +Depending on the *type*, the *precision* either indicates the number of digits that follow the decimal point (types `f` and `%`), or the number of significant digits (types `​`, `e`, `g`, `K`, `r`, `s` and `p`). If the precision is not specified, it defaults to 6 for all types except `​` (none), which defaults to 12. Precision is ignored for integer formats (types `b`, `o`, `d`, `x`, `X` and `c`). See [precisionFixed](#precisionFixed) and [precisionRound](#precisionRound) for help picking an appropriate precision. -The `~` option trims insignificant trailing zeros across all format types. This is most commonly used in conjunction with types `r`, `e`, `s` and `%`. For example: +The `~` option trims insignificant trailing zeros across all format types. This is most commonly used in conjunction with types `r`, `e`, `s`, `K` and `%`. For example: ```js d3.format("s")(1500); // "1.50000k" @@ -149,6 +149,7 @@ The available *type* values are: * `g` - either decimal or exponent notation, rounded to significant digits. * `r` - decimal notation, rounded to significant digits. * `s` - decimal notation with an [SI prefix](#locale_formatPrefix), rounded to significant digits. +* `K` - decimal notation with a [currency prefix](#locale_formatCurrencyPrefix), rounded to significant digits. * `%` - multiply by 100, and then decimal notation with a percent sign. * `p` - multiply by 100, round to significant digits, and then decimal notation with a percent sign. * `b` - binary notation, rounded to integer. @@ -167,7 +168,7 @@ d3.format(".1")(42); // "4e+1" d3.format(".1")(4.2); // "4" ``` -# locale.formatPrefix(specifier, value) [<>](https://github.com/d3/d3-format/blob/master/src/locale.js#L127 "Source") +# locale.formatPrefix(specifier, value) [<>](https://github.com/d3/d3-format/blob/master/src/locale.js#L152 "Source") Equivalent to [*locale*.format](#locale_format), except the returned function will convert values to the units of the appropriate [SI prefix](https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes) for the specified numeric reference *value* before formatting in fixed point notation. The following prefixes are supported: @@ -199,6 +200,16 @@ f(0.0042); // "4,200µ" This method is useful when formatting multiple numbers in the same units for easy comparison. See [precisionPrefix](#precisionPrefix) for help picking an appropriate precision, and [bl.ocks.org/9764126](http://bl.ocks.org/mbostock/9764126) for an example. +# locale.formatCurrencyPrefix(specifier, value) [<>](https://github.com/d3/d3-format/blob/master/src/locale.js#L153 "Source") + +Equivalent to [*locale*.locale_formatPrefix](#locale_formatPrefix), except it uses common currency abbreviations: + +* `​` (none) - 10⁰ +* `K` - thousands, 10³ +* `M` - millions, 10⁶ +* `B` - billions, 10⁹ +* `T` - trillions, 10¹² + # d3.formatSpecifier(specifier) [<>](https://github.com/d3/d3-format/blob/master/src/formatSpecifier.js "Source") Parses the specified *specifier*, returning an object with exposed fields that correspond to the [format specification mini-language](#locale_format) and a toString method that reconstructs the specifier. For example, `formatSpecifier("s")` returns: @@ -290,6 +301,10 @@ f(1.2e6); // "1.2M" f(1.3e6); // "1.3M" ``` +# d3.currencyPrecisionPrefix(step, value) [<>](https://github.com/d3/d3-format/blob/master/src/currencyPrecisionPrefix.js "Source") + +Returns a suggested decimal precision for use with [*locale*.formatCurrencyPrefix](#locale_formatCurrencyPrefix) given the specified numeric *step* and reference *value*. This is the equivalent of [*locale*.precisionPrefix](#locale_precisionPrefix) using common currency abbreviations instead of SI prefixes. + # d3.precisionRound(step, max) [<>](https://github.com/d3/d3-format/blob/master/src/precisionRound.js "Source") Returns a suggested decimal precision for format types that round to significant digits given the specified numeric *step* and *max* values. The *step* represents the minimum absolute difference between values that will be formatted, and the *max* represents the largest absolute value that will be formatted. (This assumes that the values to be formatted are also multiples of *step*.) For example, given the numbers 0.99, 1.0, and 1.01, the *step* should be 0.01, the *max* should be 1.01, and the suggested precision is 3: @@ -331,6 +346,7 @@ Returns a *locale* object for the specified *definition* with [*locale*.format]( * `thousands` - the group separator (e.g., `","`). * `grouping` - the array of group sizes (e.g., `[3]`), cycled as needed. * `currency` - the currency prefix and suffix (e.g., `["$", ""]`). +* `currencyAbbreviations` - the list of abbreviated suffixes for currency values; an array of elements for each: units, thousands, millions, billions and trillions; defaults to `["", "K", "M", "B", "T"]`. The number of elements can vary. * `numerals` - optional; an array of ten strings to replace the numerals 0-9. * `percent` - optional; the percent sign (defaults to `"%"`). * `minus` - optional; the minus sign (defaults to hyphen-minus, `"-"`). diff --git a/locale/de-DE.json b/locale/de-DE.json index a249762..320d2b0 100644 --- a/locale/de-DE.json +++ b/locale/de-DE.json @@ -2,5 +2,6 @@ "decimal": ",", "thousands": ".", "grouping": [3], - "currency": ["", "\u00a0€"] + "currency": ["", "\u00a0€"], + "currencyAbbreviations": ["", "", "\u00a0Mio.", "\u00a0Mrd.", "\u00a0Bio."] } diff --git a/locale/en-GB.json b/locale/en-GB.json index 3d22d7a..12862af 100644 --- a/locale/en-GB.json +++ b/locale/en-GB.json @@ -2,5 +2,6 @@ "decimal": ".", "thousands": ",", "grouping": [3], - "currency": ["£", ""] + "currency": ["£", ""], + "currencyAbbreviations": ["", "k", "m", "bn", "tn"] } diff --git a/locale/en-US.json b/locale/en-US.json index f075b86..d9aa81b 100644 --- a/locale/en-US.json +++ b/locale/en-US.json @@ -2,5 +2,6 @@ "decimal": ".", "thousands": ",", "grouping": [3], - "currency": ["$", ""] + "currency": ["$", ""], + "currencyAbbreviations": ["", "K", "M", "B", "T"] } diff --git a/locale/es-ES.json b/locale/es-ES.json index a249762..2ffed3a 100644 --- a/locale/es-ES.json +++ b/locale/es-ES.json @@ -2,5 +2,6 @@ "decimal": ",", "thousands": ".", "grouping": [3], - "currency": ["", "\u00a0€"] + "currency": ["", "\u00a0€"], + "currencyAbbreviations": ["", "\u00a0mil", "\u00a0M", "\u00a0mil M", "\u00a0B"] } diff --git a/locale/fr-FR.json b/locale/fr-FR.json index e0cf89d..151c02d 100644 --- a/locale/fr-FR.json +++ b/locale/fr-FR.json @@ -3,5 +3,6 @@ "thousands": "\u00a0", "grouping": [3], "currency": ["", "\u00a0€"], - "percent": "\u202f%" + "percent": "\u202f%", + "currencyAbbreviations": ["", "\u00a0k", "\u00a0M", "\u00a0Md", "\u00a0Bn"] } diff --git a/locale/it-IT.json b/locale/it-IT.json index 564ed46..202322a 100644 --- a/locale/it-IT.json +++ b/locale/it-IT.json @@ -2,5 +2,6 @@ "decimal": ",", "thousands": ".", "grouping": [3], - "currency": ["€", ""] + "currency": ["€", ""], + "currencyAbbreviations": ["", "", "\u00a0Mio", "\u00a0Mrd", "\u00a0Bln"] } diff --git a/locale/nl-NL.json b/locale/nl-NL.json index 7176b37..78ab0ab 100644 --- a/locale/nl-NL.json +++ b/locale/nl-NL.json @@ -2,5 +2,6 @@ "decimal": ",", "thousands": ".", "grouping": [3], - "currency": ["€\u00a0", ""] + "currency": ["€\u00a0", ""], + "currencyAbbreviations": ["", "K", "\u00a0mln.", "\u00a0mld.", "\u00a0bln."] } diff --git a/src/currencyPrecisionPrefix.js b/src/currencyPrecisionPrefix.js new file mode 100644 index 0000000..e4cb9c5 --- /dev/null +++ b/src/currencyPrecisionPrefix.js @@ -0,0 +1,3 @@ +import { createPrecisionPrefix } from "./precisionPrefix.js"; + +export default createPrecisionPrefix(0, 4); diff --git a/src/defaultLocale.js b/src/defaultLocale.js index 9ecf0fa..200725b 100644 --- a/src/defaultLocale.js +++ b/src/defaultLocale.js @@ -2,6 +2,7 @@ import formatLocale from "./locale.js"; var locale; export var format; +export var formatCurrencyPrefix; export var formatPrefix; defaultLocale({ @@ -15,6 +16,7 @@ defaultLocale({ export default function defaultLocale(definition) { locale = formatLocale(definition); format = locale.format; + formatCurrencyPrefix = locale.formatCurrencyPrefix; formatPrefix = locale.formatPrefix; return locale; } diff --git a/src/formatBinaryPrefixAuto.js b/src/formatBinaryPrefixAuto.js new file mode 100644 index 0000000..f835bdf --- /dev/null +++ b/src/formatBinaryPrefixAuto.js @@ -0,0 +1,46 @@ +export var binaryPrefixExponent; + +export default function(x, p) { + var binaryExponent = 0; + if (x === Infinity) return binaryPrefixExponent = 0, x; + + while (Math.round(x) >= 1024 && binaryExponent < 80) { + binaryExponent += 10; + x /= 1024; + } + + if (p <= 3 && Math.round(x) >= 1000) { + // Unlike SI prefixes, integers can take three digits. + binaryExponent += 10; + x /= 1024; + } + + binaryPrefixExponent = Math.max(0, Math.min(8, Math.floor(binaryExponent / 10))) * 10; + var i = binaryExponent - binaryPrefixExponent + 1, + coefficient = x * i, + split = ('' + coefficient).split('.'), + integer = split[0], + fraction = split[1] || '', + n = (integer + fraction).length; + + if (n === p) return coefficient; + + if (n > p) { + var fractionLength = Math.max(0, p - integer.length); + + while (+coefficient.toFixed(fractionLength) === 0) { + fractionLength += 1; + } + + coefficient = coefficient.toFixed(fractionLength); + } else { + coefficient = integer + '.' + fraction; + + while (n < p) { + coefficient += '0'; + n += 1; + } + } + + return coefficient; +} diff --git a/src/formatPrefixAuto.js b/src/formatPrefixAuto.js index c7ef7be..d11bce8 100644 --- a/src/formatPrefixAuto.js +++ b/src/formatPrefixAuto.js @@ -2,15 +2,25 @@ import formatDecimal from "./formatDecimal.js"; export var prefixExponent; -export default function(x, p) { +function formatSignificantDigitsForPrefixes(x, p, minPrefixOrder, maxPrefixOrder) { var d = formatDecimal(x, p); if (!d) return x + ""; var coefficient = d[0], exponent = d[1], - i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, + i = exponent - (prefixExponent = Math.max(minPrefixOrder, Math.min(maxPrefixOrder, Math.floor(exponent / 3))) * 3) + 1, n = coefficient.length; return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) - : "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than 1y! + : "0." + new Array(1 - i).join("0") + formatDecimal(x, Math.max(0, p + i - 1))[0]; // less than the smallest prefix +} + +export function createFormatCurrencyPrefixAutoForLocale(currencyAbbreviations) { + return function formatCurrencyPrefixAuto(x, p) { + return formatSignificantDigitsForPrefixes(x, p, 0, currencyAbbreviations.length - 1); + } +} + +export default function(x, p) { + return formatSignificantDigitsForPrefixes(x, p, -8, 8); } diff --git a/src/formatTypes.js b/src/formatTypes.js index cc7421d..a37d03e 100644 --- a/src/formatTypes.js +++ b/src/formatTypes.js @@ -1,14 +1,17 @@ -import formatPrefixAuto from "./formatPrefixAuto.js"; +import formatBinaryPrefixAuto from "./formatBinaryPrefixAuto.js"; +import formatPrefixAuto, { createFormatCurrencyPrefixAutoForLocale } from "./formatPrefixAuto.js"; import formatRounded from "./formatRounded.js"; export default { "%": function(x, p) { return (x * 100).toFixed(p); }, + "B": formatBinaryPrefixAuto, "b": function(x) { return Math.round(x).toString(2); }, "c": function(x) { return x + ""; }, "d": function(x) { return Math.round(x).toString(10); }, "e": function(x, p) { return x.toExponential(p); }, "f": function(x, p) { return x.toFixed(p); }, "g": function(x, p) { return x.toPrecision(p); }, + "K": createFormatCurrencyPrefixAutoForLocale, // depends of the current locale "o": function(x) { return Math.round(x).toString(8); }, "p": function(x, p) { return formatRounded(x * 100, p); }, "r": formatRounded, diff --git a/src/index.js b/src/index.js index 22ae6b2..61cda03 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ -export {default as formatDefaultLocale, format, formatPrefix} from "./defaultLocale.js"; +export {default as currencyPrecisionPrefix} from "./currencyPrecisionPrefix.js"; +export {default as formatDefaultLocale, format, formatCurrencyPrefix, formatPrefix} from "./defaultLocale.js"; export {default as formatLocale} from "./locale.js"; export {default as formatSpecifier, FormatSpecifier} from "./formatSpecifier.js"; export {default as precisionFixed} from "./precisionFixed.js"; diff --git a/src/locale.js b/src/locale.js index a8ea919..b023d0d 100644 --- a/src/locale.js +++ b/src/locale.js @@ -5,13 +5,17 @@ import formatSpecifier from "./formatSpecifier.js"; import formatTrim from "./formatTrim.js"; import formatTypes from "./formatTypes.js"; import {prefixExponent} from "./formatPrefixAuto.js"; +import {binaryPrefixExponent} from "./formatBinaryPrefixAuto.js"; import identity from "./identity.js"; var map = Array.prototype.map, - prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; + SIprefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"], + defaultCurrencyAbbreviations = ["", "K", "M", "B", "T"], + binaryPrefixes = ["", "Ki","Mi","Gi","Ti","Pi","Ei","Zi","Yi"]; export default function(locale) { var group = locale.grouping === undefined || locale.thousands === undefined ? identity : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), + currencyAbbreviations = locale.currencyAbbreviations === undefined ? defaultCurrencyAbbreviations : locale.currencyAbbreviations, currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", decimal = locale.decimal === undefined ? "." : locale.decimal + "", @@ -52,14 +56,18 @@ export default function(locale) { // Is this an integer type? // Can this type generate exponential notation? var formatType = formatTypes[type], - maybeSuffix = /[defgprs%]/.test(type); + maybeSuffix = /[BdefgKprs%]/.test(type); + + if (type === 'K') + formatType = formatType(currencyAbbreviations); // Set the default precision if not specified, // or clamp the specified precision to the supported range. // For significant precision, it must be in [1, 21]. // For fixed precision, it must be in [0, 20]. - precision = precision === undefined ? 6 - : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) + // For financial type, default precision is 3 significant digits instead of 6. + precision = precision === undefined ? (type === "K" ? 3 : 6) + : /[BgKprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision)); function format(value) { @@ -87,7 +95,12 @@ export default function(locale) { // Compute the prefix and suffix. valuePrefix = (valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; - valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); + switch (type) { + case "s": valueSuffix = SIprefixes[8 + prefixExponent / 3] + valueSuffix; break; + case "K": valueSuffix = currencyAbbreviations[prefixExponent / 3] + valueSuffix; break; + case "B": valueSuffix = binaryPrefixes[binaryPrefixExponent / 10] + valueSuffix; break; + } + valueSuffix += valueNegative && sign === "(" ? ")" : ""; // Break the formatted value into the integer “value” part that can be // grouped, and fractional or exponential “suffix” part that is not. @@ -131,18 +144,24 @@ export default function(locale) { return format; } - function formatPrefix(specifier, value) { - var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), - e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, + function createFormatPrefix(prefixes, minimumPrefixOrder, maximumPrefixOrder) { + return function(specifier, value) { + var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)), + e = Math.max(minimumPrefixOrder, Math.min(maximumPrefixOrder, Math.floor(exponent(value) / 3))) * 3, k = Math.pow(10, -e), - prefix = prefixes[8 + e / 3]; - return function(value) { - return f(k * value) + prefix; - }; + prefix = prefixes[(-1 * minimumPrefixOrder) + e / 3]; + return function (value) { + return f(k * value) + prefix; + }; + } } + var formatPrefix = createFormatPrefix(SIprefixes, -8, 8); + var formatCurrencyPrefix = createFormatPrefix(currencyAbbreviations, 0, currencyAbbreviations.length - 1); + return { format: newFormat, + formatCurrencyPrefix: formatCurrencyPrefix, formatPrefix: formatPrefix }; } diff --git a/src/precisionPrefix.js b/src/precisionPrefix.js index fd6af84..3e2e28e 100644 --- a/src/precisionPrefix.js +++ b/src/precisionPrefix.js @@ -1,5 +1,9 @@ import exponent from "./exponent.js"; -export default function(step, value) { - return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); +export function createPrecisionPrefix(minimumPrefixOrder, maximumPrefixOrder) { + return function (step, value) { + return Math.max(0, Math.max(minimumPrefixOrder, Math.min(maximumPrefixOrder, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); + } } + +export default createPrecisionPrefix(-8, 8); diff --git a/test/currencyPrecisionPrefix-test.js b/test/currencyPrecisionPrefix-test.js new file mode 100644 index 0000000..49aed8e --- /dev/null +++ b/test/currencyPrecisionPrefix-test.js @@ -0,0 +1,52 @@ +var tape = require("tape"), + format = require("../"); + +// For currencies, only 4 prefixes are commonly used: +// thousands (K), millions (M), billions (B) and trillions (T) + +tape("precisionPrefix(step, value) returns zero between 1 and 100 (unit step)", function (test) { + test.equal(format.currencyPrecisionPrefix(1e+0, 1e+0), 0); // 1 + test.equal(format.currencyPrecisionPrefix(1e+0, 1e+1), 0); // 10 + test.equal(format.currencyPrecisionPrefix(1e+0, 1e+2), 0); // 100 + test.end() +}); + +tape("precisionPrefix(step, value) returns zero between 1 and 100 (thousand step)", function (test) { + test.equal(format.currencyPrecisionPrefix(1e+3, 1e+3), 0); // 1K + test.equal(format.currencyPrecisionPrefix(1e+3, 1e+4), 0); // 10K + test.equal(format.currencyPrecisionPrefix(1e+3, 1e+5), 0); // 100K + test.end() +}); + +tape("precisionPrefix(step, value) returns zero between 1 and 100 (million step)", function (test) { + test.equal(format.currencyPrecisionPrefix(1e+6, 1e+6), 0); // 1M + test.equal(format.currencyPrecisionPrefix(1e+6, 1e+7), 0); // 10M + test.equal(format.currencyPrecisionPrefix(1e+6, 1e+8), 0); // 100M + test.end() +}); + +tape("precisionPrefix(step, value) returns zero between 1 and 100 (billion step)", function (test) { + test.equal(format.currencyPrecisionPrefix(1e+9, 1e+9), 0); // 1B + test.equal(format.currencyPrecisionPrefix(1e+9, 1e+10), 0); // 10B + test.equal(format.currencyPrecisionPrefix(1e+9, 1e+11), 0); // 100B + test.end() +}); + +tape("currencyPrecisionPrefix(step, value) returns the expected precision when value is greater than one trillion", function(test) { + test.equal(format.currencyPrecisionPrefix(1e+12, 1e+12), 0); // 1T + test.equal(format.currencyPrecisionPrefix(1e+12, 1e+13), 0); // 10T + test.equal(format.currencyPrecisionPrefix(1e+12, 1e+14), 0); // 100T + test.equal(format.currencyPrecisionPrefix(1e+12, 1e+15), 0); // 1000T + test.equal(format.currencyPrecisionPrefix(1e+11, 1e+15), 1); // 1000.0T + test.end(); +}); + +tape("currencyPrecisionPrefix(step, value) returns the expected precision when value is less than one unit", function(test) { + test.equal(format.currencyPrecisionPrefix(1e+0, 1e+0), 0); // 1 + test.equal(format.currencyPrecisionPrefix(1e-1, 1e-1), 1); // 0.1 + test.equal(format.currencyPrecisionPrefix(1e-2, 1e-2), 2); // 0.01 + test.equal(format.currencyPrecisionPrefix(1e-3, 1e-3), 3); // 0.001 + test.equal(format.currencyPrecisionPrefix(1e-4, 1e-4), 4); // 0.0001 + test.end(); +}); + diff --git a/test/format-type-K-test.js b/test/format-type-K-test.js new file mode 100644 index 0000000..ed5c1ab --- /dev/null +++ b/test/format-type-K-test.js @@ -0,0 +1,91 @@ +var tape = require("tape"), + format = require("../dist/d3-format"); + +tape("format(\"K\") outputs currency prefix notation with default 3 significant digits", function(test) { + var f = format.format("K"); + test.equal(f(0), "0.00"); + test.equal(f(0.2), "0.20"); + test.equal(f(1), "1.00"); + test.equal(f(10), "10.0"); + test.equal(f(100), "100"); + test.equal(f(1000), "1.00K"); + test.end(); +}); + +tape("format(\"[.precision]K\") outputs currency-prefix notation with precision significant digits", function(test) { + var f1 = format.format(".2K"); + test.equal(f1(0), "0.0"); + test.equal(f1(1), "1.0"); + test.equal(f1(10), "10"); + test.equal(f1(100), "100"); + test.equal(f1(999.5), "1.0K"); + test.equal(f1(999500), "1.0M"); + test.equal(f1(1000), "1.0K"); + test.equal(f1(1500.5), "1.5K"); + test.equal(f1(145500000), "150M"); + test.equal(f1(145999999.999999347), "150M"); + test.equal(f1(1e17), "100000T"); + test.equal(f1(.000001), "0.000001"); + var f2 = format.format(".4K"); + test.equal(f2(999.5), "999.5"); + test.equal(f2(999500), "999.5K"); + test.end(); +}); + +tape("format(\"K\") formats numbers smaller than 1", function(test) { + var f = format.format(".2K"); + test.equal(f(1.29e-2), "0.0129"); + test.equal(f(1.29e-3), "0.00129"); + test.equal(f(-1.29e-2), "-0.0129"); + test.equal(f(-1.29e-3), "-0.00129"); + test.end(); +}); + +tape("format(\"K\") formats numbers larger than thousands of trillions", function(test) { + var f = format.format(".2K"); + test.equal(f(1.23e+15), "1200T"); + test.equal(f(1.23e+16), "12000T"); + test.equal(f(-1.23e+15), "-1200T"); + test.equal(f(-1.23e+16), "-12000T"); + test.end(); +}); + +tape("format(\"$K\") outputs currency-prefix notation with a currency symbol", function(test) { + var f1 = format.format("$.2K"); + test.equal(f1(0), "$0.0"); + test.equal(f1(2.5e5), "$250K"); + test.equal(f1(-2.5e8), "-$250M"); + test.equal(f1(2.5e11), "$250B"); + var f2 = format.format("$.3K"); + test.equal(f2(0), "$0.00"); + test.equal(f2(1), "$1.00"); + test.equal(f2(10), "$10.0"); + test.equal(f2(100), "$100"); + test.equal(f2(999.5), "$1.00K"); + test.equal(f2(999500), "$1.00M"); + test.equal(f2(1000), "$1.00K"); + test.equal(f2(1500.5), "$1.50K"); + test.equal(f2(145500000), "$146M"); + test.equal(f2(145999999.999999347), "$146M"); + test.equal(f2(1e18), "$1000000T"); + test.equal(f2(.000001), "$0.000001"); + test.equal(f2(.009995), "$0.01"); + var f3 = format.format("$.4K"); + test.equal(f3(999.5), "$999.5"); + test.equal(f3(999500), "$999.5K"); + test.equal(f3(.009995), "$0.001"); + test.end(); +}); + +tape("format(\"0[width],K\") will group thousands due to zero fill", function(test) { + var f = format.format("015,K"); + test.equal(f(42), "0,000,000,042.0"); + test.equal(f(42e12), "0,000,000,042.0T"); + test.end(); +}); + +tape("format(\",K\") will group thousands for very large numbers", function(test) { + var f = format.format(",K"); + test.equal(f(42e30), "42,000,000,000,000,000,000T"); + test.end(); +}); diff --git a/test/format-type-bi-test.js b/test/format-type-bi-test.js new file mode 100644 index 0000000..6ac73cf --- /dev/null +++ b/test/format-type-bi-test.js @@ -0,0 +1,151 @@ +var tape = require("tape"), + format = require("../"); + +tape("format(\"B\") outputs binary-prefix notation with default precision 6", function(test) { + var f = format.format("B"); + test.equal(f(0), "0.00000"); + test.equal(f(1), "1.00000"); + test.equal(f(10), "10.0000"); + test.equal(f(100), "100.000"); + test.equal(f(999.5), "999.500"); + test.equal(f(1000), "1000.00"); + test.equal(f(999500), "976.074Ki"); + test.equal(f(1000000), "976.563Ki"); + test.equal(f(100), "100.000"); + test.equal(f(1024), "1.00000Ki"); + test.equal(f(1280), "1.25000Ki"); + test.equal(f(1536.512), "1.50050Ki"); + test.equal(f(.00001), "0.00001"); + test.equal(f(.000001), "0.000001"); + test.end(); +}); + +tape("format(\"[.precision]B\") outputs binary-prefix notation with precision significant digits", function(test) { + var f1 = format.format(".3B"); + test.equal(f1(0), "0.00"); + test.equal(f1(1), "1.00"); + test.equal(f1(10), "10.0"); + test.equal(f1(100), "100"); + test.equal(f1(1023.5), "1.00Ki"); + test.equal(f1(1048576), "1.00Mi"); + test.equal(f1(1048064), "1.00Mi"); + test.equal(f1(1040000), "0.99Mi"); + test.equal(f1(1024), "1.00Ki"); + test.equal(f1(1536), "1.50Ki"); + test.equal(f1(152567808), "146Mi"); // 145.5Mi + test.equal(f1(152567807), "145Mi"); // 145.499999Mi + test.equal(f1(100 * Math.pow(2, 80)), "100Yi"); + var f2 = format.format(".4B"); + test.equal(f2(999.5), "999.5"); + test.equal(f2(1000), "1000"); + test.equal(f2(999.5 * 1024), "999.5Ki"); + test.equal(f2(1000 * 1024), "1000Ki"); + test.end(); +}); + +tape("format(\"B\") formats numbers smaller than 1", function(test) { + var f = format.format(".8B"); + test.equal(f(1.29e-6), "0.0000013"); // Note: rounded! + test.equal(f(1.29e-5), "0.0000129"); + test.equal(f(1.29e-4), "0.0001290"); + test.equal(f(1.29e-3), "0.0012900"); + test.equal(f(1.29e-2), "0.0129000"); + test.equal(f(1.29e-1), "0.1290000"); + test.end(); +}); + +tape("format(\"B\") formats numbers larger than 2**80 with yobi", function(test) { + var f = format.format(".8B"); + test.equal(f(1.23 * Math.pow(2, 70)), "1.2300000Zi"); + test.equal(f(12.3 * Math.pow(2, 70)), "12.300000Zi"); + test.equal(f(123 * Math.pow(2, 70)), "123.00000Zi"); + test.equal(f(1.23 * Math.pow(2, 80)), "1.2300000Yi"); + test.equal(f(12.3 * Math.pow(2, 80)), "12.300000Yi"); + test.equal(f(123 * Math.pow(2, 80)), "123.00000Yi"); + test.equal(f(1230 * Math.pow(2, 80)), "1230.0000Yi"); + test.equal(f(12300 * Math.pow(2, 80)), "12300.000Yi"); + test.equal(f(123000 * Math.pow(2, 80)), "123000.00Yi"); + test.equal(f(1230000 * Math.pow(2, 80)), "1230000.0Yi"); + test.equal(f(1234567.89 * Math.pow(2, 80)), "1234567.9Yi"); + test.equal(f(-1.23 * Math.pow(2, 70)), "-1.2300000Zi"); + test.equal(f(-12.3 * Math.pow(2, 70)), "-12.300000Zi"); + test.equal(f(-123 * Math.pow(2, 70)), "-123.00000Zi"); + test.equal(f(-1.23 * Math.pow(2, 80)), "-1.2300000Yi"); + test.equal(f(-12.3 * Math.pow(2, 80)), "-12.300000Yi"); + test.equal(f(-123 * Math.pow(2, 80)), "-123.00000Yi"); + test.equal(f(-1230 * Math.pow(2, 80)), "-1230.0000Yi"); + test.equal(f(-12300 * Math.pow(2, 80)), "-12300.000Yi"); + test.equal(f(-123000 * Math.pow(2, 80)), "-123000.00Yi"); + test.equal(f(-1230000 * Math.pow(2, 80)), "-1230000.0Yi"); + test.equal(f(-1234567.89 * Math.pow(2, 80)), "-1234567.9Yi"); + test.end(); +}); + +tape("format(\"$B\") outputs binary-prefix notation with a currency symbol", function(test) { + var f1 = format.format("$.2B"); + test.equal(f1(0), "$0.0"); + test.equal(f1(256000), "$250Ki"); + test.equal(f1(-250 * Math.pow(2, 20)), "-$250Mi"); + test.equal(f1(250 * Math.pow(2, 30)), "$250Gi"); + var f2 = format.format("$.3B"); + test.equal(f2(0), "$0.00"); + test.equal(f2(1), "$1.00"); + test.equal(f2(10), "$10.0"); + test.equal(f2(100), "$100"); + test.equal(f2(999.4), "$999"); + test.equal(f2(999.5), "$0.98Ki"); + test.equal(f2(.9995 * Math.pow(2, 10)), "$1.00Ki"); + test.equal(f2(.9995 * Math.pow(2, 20)), "$1.00Mi"); + test.equal(f2(1024), "$1.00Ki"); + test.equal(f2(1535.5), "$1.50Ki"); + test.equal(f2(152567808), "$146Mi"); + test.equal(f2(152567807), "$145Mi"); + test.equal(f2(100 * Math.pow(2, 80)), "$100Yi"); + test.equal(f2(.000001), "$0.000001"); + test.equal(f2(.009995), "$0.01"); + var f3 = format.format("$.4B"); + test.equal(f3(1023), "$1023"); + test.equal(f3(1023 * Math.pow(2, 10)), "$1023Ki"); + var f4 = format.format("$.5B"); + test.equal(f4(1023.5), "$0.9995Ki"); + test.equal(f4(1023.5 * Math.pow(2, 10)), "$0.9995Mi"); + test.end(); +}); + +tape("format(\"B\") binary-prefix notation precision is consistent for small and large numbers", function(test) { + var f1 = format.format(".0B"); + test.equal(f1(1e0 * Math.pow(2, 0)), "1"); + test.equal(f1(1e1 * Math.pow(2, 0)), "10"); + test.equal(f1(1e2 * Math.pow(2, 0)), "100"); + test.equal(f1(1e0 * Math.pow(2, 10)), "1Ki"); + test.equal(f1(1e1 * Math.pow(2, 10)), "10Ki"); + test.equal(f1(1e2 * Math.pow(2, 10)), "100Ki"); + var f2 = format.format(".4B"); + test.equal(f2(1e+0 * Math.pow(2, 0)), "1.000"); + test.equal(f2(1e+1 * Math.pow(2, 0)), "10.00"); + test.equal(f2(1e+2 * Math.pow(2, 0)), "100.0"); + test.equal(f2(1e+0 * Math.pow(2, 10)), "1.000Ki"); + test.equal(f2(1e+1 * Math.pow(2, 10)), "10.00Ki"); + test.equal(f2(1e+2 * Math.pow(2, 10)), "100.0Ki"); + test.end(); +}); + +tape("format(\"0[width],B\") will group thousands due to zero fill", function(test) { + var f = format.format("020,B"); + test.equal(f(42), "000,000,000,042.0000"); + test.equal(f(42 * Math.pow(2, 40)), "0,000,000,042.0000Ti"); + test.end(); +}); + +tape("format(\",B\") will group thousands for very large numbers", function(test) { + var f = format.format(",B"); + test.equal(f(42e6 * Math.pow(2, 80)), "42,000,000Yi"); + test.end(); +}); + +tape("format(\"B\") will not hang on Infinity", function(test) { + var f = format.format("B"); + test.equal(f(Infinity), "Infinity"); + test.equal(f(-Infinity), "-Infinity"); + test.end(); +}); diff --git a/test/formatCurrencyPrefix-test.js b/test/formatCurrencyPrefix-test.js new file mode 100644 index 0000000..23fa7b4 --- /dev/null +++ b/test/formatCurrencyPrefix-test.js @@ -0,0 +1,48 @@ +var tape = require("tape"), + format = require("../dist/d3-format"); + +tape("formatCurrencyPrefix(\"K\", value)(number) formats with the thousands prefix if appropriate to the specified value", function(test) { + test.equal(format.formatCurrencyPrefix(",.0K", 1e3)(42000), "42K"); + test.equal(format.formatCurrencyPrefix(",.0K", 1e3)(420000), "420K"); + test.equal(format.formatCurrencyPrefix(",.3K", 1e3)(420), "0.420K"); + test.end(); +}); + +tape("formatCurrencyPrefix(\"K\", value)(number) formats with the millions prefix if appropriate to the specified value", function(test) { + test.equal(format.formatCurrencyPrefix(",.0K", 1e6)(42000000), "42M"); + test.equal(format.formatCurrencyPrefix(",.0K", 1e6)(420000000), "420M"); + test.equal(format.formatCurrencyPrefix(",.3K", 1e6)(420000), "0.420M"); + test.end(); +}); + +tape("formatCurrencyPrefix(\"K\", value)(number) formats with the billions prefix if appropriate to the specified value", function(test) { + test.equal(format.formatCurrencyPrefix(",.0K", 1e9)(42 * 1e9), "42B"); + test.equal(format.formatCurrencyPrefix(",.0K", 1e9)(420 * 1e9), "420B"); + test.equal(format.formatCurrencyPrefix(",.3K", 1e9)(420 * 1e6), "0.420B"); + test.end(); +}); + +tape("formatCurrencyPrefix(\"K\", value)(number) formats with the trillions prefix if appropriate to the specified value", function(test) { + test.equal(format.formatCurrencyPrefix(",.0K", 1e12)(42 * 1e12), "42T"); + test.equal(format.formatCurrencyPrefix(",.0K", 1e12)(420 * 1e12), "420T"); + test.equal(format.formatCurrencyPrefix(",.3K", 1e12)(420 * 1e9), "0.420T"); + test.end(); +}); + + +tape("formatCurrencyPrefix(\"K\", value)(number) uses nothing for very small reference values", function(test) { + test.equal(format.formatCurrencyPrefix(",.3K", 1e-3)(0.1), "0.100"); + test.end(); +}); + +tape("formatCurrencyPrefix(\"K\", value)(number) uses trillions for very large reference values", function(test) { + test.equal(format.formatCurrencyPrefix(",.0K", 1e15)(1e15), "1,000T"); + test.end(); +}); + +tape("formatCurrencyPrefix(\"$,K\", value)(number) formats with the associated currency", function(test) { + var f = format.formatCurrencyPrefix(" $12,.1K", 1e9); + test.equal(f(-42e9), " -$42.0B"); + test.equal(f(+4.2e9), " $4.2B"); + test.end(); +}); diff --git a/test/locale-test.js b/test/locale-test.js index 394811d..dc3716c 100644 --- a/test/locale-test.js +++ b/test/locale-test.js @@ -21,6 +21,16 @@ tape("formatLocale({currency: [prefix, suffix]}) places the currency suffix afte test.end(); }); +tape("formatLocale({currencyAbbreviations: [list of abbreviations]}) should abbreviate thousands, millions, billions and trillions", function (test) { + test.equal(d3.formatLocale({ currencyAbbreviations: ["", "\u00a0k", "\u00a0M", "\u00a0Md", "\u00a0Bn"] }).format("$.3K")(1.2e9), "1.20\u00a0Md"); + test.end(); +}); + +tape("formatLocale({currencyAbbreviations: [list of abbreviations]}) should abbreviate only specified levels", function (test) { + test.equal(d3.formatLocale({ currencyAbbreviations: ["", "\u00a0k", "\u00a0M", "\u00a0Md", "\u00a0Bn"] }).format("$.3K")(1.2e15), "1200\u00a0Bn"); + test.end(); +}); + tape("formatLocale({grouping: undefined}) does not perform any grouping", function(test) { test.equal(d3.formatLocale({decimal: "."}).format("012,.2f")(2), "000000002.00"); test.end();