Skip to content

Commit eebecf0

Browse files
Rúnar BergRúnar Berg
Rúnar Berg
authored and
Rúnar Berg
committed
Implement binary prefixes
Closes: #33
1 parent f0232d0 commit eebecf0

File tree

4 files changed

+200
-3
lines changed

4 files changed

+200
-3
lines changed

src/formatBinaryPrefixAuto.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export var binaryPrefixExponent;
2+
3+
export default function(x, p) {
4+
var binaryExponent = 0;
5+
6+
while (Math.round(x) >= 1024 && binaryExponent < 80) {
7+
binaryExponent += 10;
8+
x /= 1024;
9+
}
10+
11+
if (p <= 3 && Math.round(x) >= 1000) {
12+
// Unlike SI prefixes, intergers can take three digits.
13+
binaryExponent += 10;
14+
x /= 1024;
15+
}
16+
17+
binaryPrefixExponent = Math.max(0, Math.min(8, Math.floor(binaryExponent / 10))) * 10;
18+
var i = binaryExponent - binaryPrefixExponent + 1,
19+
coefficient = x * i,
20+
split = ('' + coefficient).split('.'),
21+
integer = split[0],
22+
fraction = split[1] || '',
23+
n = (integer + fraction).length;
24+
25+
if (n === p) return coefficient;
26+
27+
if (n > p) {
28+
var fractionLength = Math.max(0, p - integer.length);
29+
30+
while (+coefficient.toFixed(fractionLength) === 0) {
31+
fractionLength += 1;
32+
}
33+
34+
coefficient = coefficient.toFixed(fractionLength);
35+
} else {
36+
coefficient = integer + '.' + fraction;
37+
38+
while (n < p) {
39+
coefficient += '0';
40+
n += 1;
41+
}
42+
}
43+
44+
return coefficient;
45+
}

src/formatTypes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import formatBinaryPrefixAuto from "./formatBinaryPrefixAuto";
12
import formatPrefixAuto from "./formatPrefixAuto";
23
import formatRounded from "./formatRounded";
34

45
export default {
56
"%": function(x, p) { return (x * 100).toFixed(p); },
7+
"B": formatBinaryPrefixAuto,
68
"b": function(x) { return Math.round(x).toString(2); },
79
"c": function(x) { return x + ""; },
810
"d": function(x) { return Math.round(x).toString(10); },

src/locale.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import formatSpecifier from "./formatSpecifier";
55
import formatTrim from "./formatTrim";
66
import formatTypes from "./formatTypes";
77
import {prefixExponent} from "./formatPrefixAuto";
8+
import {binaryPrefixExponent} from "./formatBinaryPrefixAuto";
89
import identity from "./identity";
910

1011
var prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];
12+
var binaryPrefixes = ["", "Ki","Mi","Gi","Ti","Pi","Ei","Zi","Yi"];
1113

1214
export default function(locale) {
1315
var group = locale.grouping && locale.thousands ? formatGroup(locale.grouping, locale.thousands) : identity,
@@ -48,14 +50,14 @@ export default function(locale) {
4850
// Is this an integer type?
4951
// Can this type generate exponential notation?
5052
var formatType = formatTypes[type],
51-
maybeSuffix = /[defgprs%]/.test(type);
53+
maybeSuffix = /[Bdefgprs%]/.test(type);
5254

5355
// Set the default precision if not specified,
5456
// or clamp the specified precision to the supported range.
5557
// For significant precision, it must be in [1, 21].
5658
// For fixed precision, it must be in [0, 20].
5759
precision = precision == null ? 6
58-
: /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision))
60+
: /[Bgprs]/.test(type) ? Math.max(1, Math.min(21, precision))
5961
: Math.max(0, Math.min(20, precision));
6062

6163
function format(value) {
@@ -81,7 +83,11 @@ export default function(locale) {
8183

8284
// Compute the prefix and suffix.
8385
valuePrefix = (valueNegative ? (sign === "(" ? sign : "-") : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
84-
valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : "");
86+
valueSuffix = (
87+
type === "s" ? prefixes[8 + prefixExponent / 3]
88+
: type === "B" ? binaryPrefixes[binaryPrefixExponent / 10]
89+
: ""
90+
) + valueSuffix + (valueNegative && sign === "(" ? ")" : "");
8591

8692
// Break the formatted value into the integer “value” part that can be
8793
// grouped, and fractional or exponential “suffix” part that is not.

test/format-type-bi-test.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
var tape = require("tape"),
2+
format = require("../");
3+
4+
tape("format(\"B\") outputs binary-prefix notation with default precision 6", function(test) {
5+
var f = format.format("B");
6+
test.equal(f(0), "0.00000");
7+
test.equal(f(1), "1.00000");
8+
test.equal(f(10), "10.0000");
9+
test.equal(f(100), "100.000");
10+
test.equal(f(999.5), "999.500");
11+
test.equal(f(1000), "1000.00");
12+
test.equal(f(999500), "976.074Ki");
13+
test.equal(f(1000000), "976.563Ki");
14+
test.equal(f(100), "100.000");
15+
test.equal(f(1024), "1.00000Ki");
16+
test.equal(f(1280), "1.25000Ki");
17+
test.equal(f(1536.512), "1.50050Ki");
18+
test.equal(f(.00001), "0.00001");
19+
test.equal(f(.000001), "0.000001");
20+
test.end();
21+
});
22+
23+
tape("format(\"[.precision]B\") outputs binary-prefix notation with precision significant digits", function(test) {
24+
var f1 = format.format(".3B");
25+
test.equal(f1(0), "0.00");
26+
test.equal(f1(1), "1.00");
27+
test.equal(f1(10), "10.0");
28+
test.equal(f1(100), "100");
29+
test.equal(f1(1023.5), "1.00Ki");
30+
test.equal(f1(1048576), "1.00Mi");
31+
test.equal(f1(1048064), "1.00Mi");
32+
test.equal(f1(1040000), "0.99Mi");
33+
test.equal(f1(1024), "1.00Ki");
34+
test.equal(f1(1536), "1.50Ki");
35+
test.equal(f1(152567808), "146Mi"); // 145.5Mi
36+
test.equal(f1(152567807), "145Mi"); // 145.499999Mi
37+
test.equal(f1(100 * Math.pow(2, 80)), "100Yi");
38+
var f2 = format.format(".4B");
39+
test.equal(f2(999.5), "999.5");
40+
test.equal(f2(1000), "1000");
41+
test.equal(f2(999.5 * 1024), "999.5Ki");
42+
test.equal(f2(1000 * 1024), "1000Ki");
43+
test.end();
44+
});
45+
46+
tape("format(\"B\") formats numbers smaller than 1", function(test) {
47+
var f = format.format(".8B");
48+
test.equal(f(1.29e-6), "0.0000013"); // Note: rounded!
49+
test.equal(f(1.29e-5), "0.0000129");
50+
test.equal(f(1.29e-4), "0.0001290");
51+
test.equal(f(1.29e-3), "0.0012900");
52+
test.equal(f(1.29e-2), "0.0129000");
53+
test.equal(f(1.29e-1), "0.1290000");
54+
test.end();
55+
});
56+
57+
tape("format(\"B\") formats numbers larger than 2**80 with yobi", function(test) {
58+
var f = format.format(".8B");
59+
test.equal(f(1.23 * Math.pow(2, 70)), "1.2300000Zi");
60+
test.equal(f(12.3 * Math.pow(2, 70)), "12.300000Zi");
61+
test.equal(f(123 * Math.pow(2, 70)), "123.00000Zi");
62+
test.equal(f(1.23 * Math.pow(2, 80)), "1.2300000Yi");
63+
test.equal(f(12.3 * Math.pow(2, 80)), "12.300000Yi");
64+
test.equal(f(123 * Math.pow(2, 80)), "123.00000Yi");
65+
test.equal(f(1230 * Math.pow(2, 80)), "1230.0000Yi");
66+
test.equal(f(12300 * Math.pow(2, 80)), "12300.000Yi");
67+
test.equal(f(123000 * Math.pow(2, 80)), "123000.00Yi");
68+
test.equal(f(1230000 * Math.pow(2, 80)), "1230000.0Yi");
69+
test.equal(f(1234567.89 * Math.pow(2, 80)), "1234567.9Yi");
70+
test.equal(f(-1.23 * Math.pow(2, 70)), "-1.2300000Zi");
71+
test.equal(f(-12.3 * Math.pow(2, 70)), "-12.300000Zi");
72+
test.equal(f(-123 * Math.pow(2, 70)), "-123.00000Zi");
73+
test.equal(f(-1.23 * Math.pow(2, 80)), "-1.2300000Yi");
74+
test.equal(f(-12.3 * Math.pow(2, 80)), "-12.300000Yi");
75+
test.equal(f(-123 * Math.pow(2, 80)), "-123.00000Yi");
76+
test.equal(f(-1230 * Math.pow(2, 80)), "-1230.0000Yi");
77+
test.equal(f(-12300 * Math.pow(2, 80)), "-12300.000Yi");
78+
test.equal(f(-123000 * Math.pow(2, 80)), "-123000.00Yi");
79+
test.equal(f(-1230000 * Math.pow(2, 80)), "-1230000.0Yi");
80+
test.equal(f(-1234567.89 * Math.pow(2, 80)), "-1234567.9Yi");
81+
test.end();
82+
});
83+
84+
tape("format(\"$B\") outputs binary-prefix notation with a currency symbol", function(test) {
85+
var f1 = format.format("$.2B");
86+
test.equal(f1(0), "$0.0");
87+
test.equal(f1(256000), "$250Ki");
88+
test.equal(f1(-250 * Math.pow(2, 20)), "-$250Mi");
89+
test.equal(f1(250 * Math.pow(2, 30)), "$250Gi");
90+
var f2 = format.format("$.3B");
91+
test.equal(f2(0), "$0.00");
92+
test.equal(f2(1), "$1.00");
93+
test.equal(f2(10), "$10.0");
94+
test.equal(f2(100), "$100");
95+
test.equal(f2(999.4), "$999");
96+
test.equal(f2(999.5), "$0.98Ki");
97+
test.equal(f2(.9995 * Math.pow(2, 10)), "$1.00Ki");
98+
test.equal(f2(.9995 * Math.pow(2, 20)), "$1.00Mi");
99+
test.equal(f2(1024), "$1.00Ki");
100+
test.equal(f2(1535.5), "$1.50Ki");
101+
test.equal(f2(152567808), "$146Mi");
102+
test.equal(f2(152567807), "$145Mi");
103+
test.equal(f2(100 * Math.pow(2, 80)), "$100Yi");
104+
test.equal(f2(.000001), "$0.000001");
105+
test.equal(f2(.009995), "$0.01");
106+
var f3 = format.format("$.4B");
107+
test.equal(f3(1023), "$1023");
108+
test.equal(f3(1023 * Math.pow(2, 10)), "$1023Ki");
109+
var f4 = format.format("$.5B");
110+
test.equal(f4(1023.5), "$0.9995Ki");
111+
test.equal(f4(1023.5 * Math.pow(2, 10)), "$0.9995Mi");
112+
test.end();
113+
});
114+
115+
tape("format(\"B\") binary-prefix notation precision is consistent for small and large numbers", function(test) {
116+
var f1 = format.format(".0B");
117+
test.equal(f1(1e0 * Math.pow(2, 0)), "1");
118+
test.equal(f1(1e1 * Math.pow(2, 0)), "10");
119+
test.equal(f1(1e2 * Math.pow(2, 0)), "100");
120+
test.equal(f1(1e0 * Math.pow(2, 10)), "1Ki");
121+
test.equal(f1(1e1 * Math.pow(2, 10)), "10Ki");
122+
test.equal(f1(1e2 * Math.pow(2, 10)), "100Ki");
123+
var f2 = format.format(".4B");
124+
test.equal(f2(1e+0 * Math.pow(2, 0)), "1.000");
125+
test.equal(f2(1e+1 * Math.pow(2, 0)), "10.00");
126+
test.equal(f2(1e+2 * Math.pow(2, 0)), "100.0");
127+
test.equal(f2(1e+0 * Math.pow(2, 10)), "1.000Ki");
128+
test.equal(f2(1e+1 * Math.pow(2, 10)), "10.00Ki");
129+
test.equal(f2(1e+2 * Math.pow(2, 10)), "100.0Ki");
130+
test.end();
131+
});
132+
133+
tape("format(\"0[width],B\") will group thousands due to zero fill", function(test) {
134+
var f = format.format("020,B");
135+
test.equal(f(42), "000,000,000,042.0000");
136+
test.equal(f(42 * Math.pow(2, 40)), "0,000,000,042.0000Ti");
137+
test.end();
138+
});
139+
140+
tape("format(\",B\") will group thousands for very large numbers", function(test) {
141+
var f = format.format(",B");
142+
test.equal(f(42e6 * Math.pow(2, 80)), "42,000,000Yi");
143+
test.end();
144+
});

0 commit comments

Comments
 (0)