1 | import { mapObject } from '../object'
|
2 |
|
3 | /**
|
4 | * Convert a BigNumber to a formatted string representation.
|
5 | *
|
6 | * Syntax:
|
7 | *
|
8 | * format(value)
|
9 | * format(value, options)
|
10 | * format(value, precision)
|
11 | * format(value, fn)
|
12 | *
|
13 | * Where:
|
14 | *
|
15 | * {number} value The value to be formatted
|
16 | * {Object} options An object with formatting options. Available options:
|
17 | * {string} notation
|
18 | * Number notation. Choose from:
|
19 | * 'fixed' Always use regular number notation.
|
20 | * For example '123.40' and '14000000'
|
21 | * 'exponential' Always use exponential notation.
|
22 | * For example '1.234e+2' and '1.4e+7'
|
23 | * 'auto' (default) Regular number notation for numbers
|
24 | * having an absolute value between
|
25 | * `lower` and `upper` bounds, and uses
|
26 | * exponential notation elsewhere.
|
27 | * Lower bound is included, upper bound
|
28 | * is excluded.
|
29 | * For example '123.4' and '1.4e7'.
|
30 | * {number} precision A number between 0 and 16 to round
|
31 | * the digits of the number.
|
32 | * In case of notations 'exponential',
|
33 | * 'engineering', and 'auto',
|
34 | * `precision` defines the total
|
35 | * number of significant digits returned.
|
36 | * In case of notation 'fixed',
|
37 | * `precision` defines the number of
|
38 | * significant digits after the decimal
|
39 | * point.
|
40 | * `precision` is undefined by default.
|
41 | * {number} lowerExp Exponent determining the lower boundary
|
42 | * for formatting a value with an exponent
|
43 | * when `notation='auto`.
|
44 | * Default value is `-3`.
|
45 | * {number} upperExp Exponent determining the upper boundary
|
46 | * for formatting a value with an exponent
|
47 | * when `notation='auto`.
|
48 | * Default value is `5`.
|
49 | * {Function} fn A custom formatting function. Can be used to override the
|
50 | * built-in notations. Function `fn` is called with `value` as
|
51 | * parameter and must return a string. Is useful for example to
|
52 | * format all values inside a matrix in a particular way.
|
53 | *
|
54 | * Examples:
|
55 | *
|
56 | * format(6.4) // '6.4'
|
57 | * format(1240000) // '1.24e6'
|
58 | * format(1/3) // '0.3333333333333333'
|
59 | * format(1/3, 3) // '0.333'
|
60 | * format(21385, 2) // '21000'
|
61 | * format(12e8, {notation: 'fixed'}) // returns '1200000000'
|
62 | * format(2.3, {notation: 'fixed', precision: 4}) // returns '2.3000'
|
63 | * format(52.8, {notation: 'exponential'}) // returns '5.28e+1'
|
64 | * format(12400, {notation: 'engineering'}) // returns '12.400e+3'
|
65 | *
|
66 | * @param {BigNumber} value
|
67 | * @param {Object | Function | number} [options]
|
68 | * @return {string} str The formatted value
|
69 | */
|
70 | export function format (value, options) {
|
71 | if (typeof options === 'function') {
|
72 | // handle format(value, fn)
|
73 | return options(value)
|
74 | }
|
75 |
|
76 | // handle special cases
|
77 | if (!value.isFinite()) {
|
78 | return value.isNaN() ? 'NaN' : (value.gt(0) ? 'Infinity' : '-Infinity')
|
79 | }
|
80 |
|
81 | // default values for options
|
82 | let notation = 'auto'
|
83 | let precision
|
84 |
|
85 | if (options !== undefined) {
|
86 | // determine notation from options
|
87 | if (options.notation) {
|
88 | notation = options.notation
|
89 | }
|
90 |
|
91 | // determine precision from options
|
92 | if (typeof options === 'number') {
|
93 | precision = options
|
94 | } else if (options.precision) {
|
95 | precision = options.precision
|
96 | }
|
97 | }
|
98 |
|
99 | // handle the various notations
|
100 | switch (notation) {
|
101 | case 'fixed':
|
102 | return toFixed(value, precision)
|
103 |
|
104 | case 'exponential':
|
105 | return toExponential(value, precision)
|
106 |
|
107 | case 'engineering':
|
108 | return toEngineering(value, precision)
|
109 |
|
110 | case 'auto':
|
111 | // TODO: clean up some day. Deprecated since: 2018-01-24
|
112 | // @deprecated upper and lower are replaced with upperExp and lowerExp since v4.0.0
|
113 | if (options && options.exponential && (options.exponential.lower !== undefined || options.exponential.upper !== undefined)) {
|
114 | const fixedOptions = mapObject(options, function (x) { return x })
|
115 | fixedOptions.exponential = undefined
|
116 | if (options.exponential.lower !== undefined) {
|
117 | fixedOptions.lowerExp = Math.round(Math.log(options.exponential.lower) / Math.LN10)
|
118 | }
|
119 | if (options.exponential.upper !== undefined) {
|
120 | fixedOptions.upperExp = Math.round(Math.log(options.exponential.upper) / Math.LN10)
|
121 | }
|
122 |
|
123 | console.warn('Deprecation warning: Formatting options exponential.lower and exponential.upper ' +
|
124 | '(minimum and maximum value) ' +
|
125 | 'are replaced with exponential.lowerExp and exponential.upperExp ' +
|
126 | '(minimum and maximum exponent) since version 4.0.0. ' +
|
127 | 'Replace ' + JSON.stringify(options) + ' with ' + JSON.stringify(fixedOptions))
|
128 |
|
129 | return format(value, fixedOptions)
|
130 | }
|
131 |
|
132 | // determine lower and upper bound for exponential notation.
|
133 | // TODO: implement support for upper and lower to be BigNumbers themselves
|
134 | const lowerExp = (options && options.lowerExp !== undefined) ? options.lowerExp : -3
|
135 | const upperExp = (options && options.upperExp !== undefined) ? options.upperExp : 5
|
136 |
|
137 | // handle special case zero
|
138 | if (value.isZero()) return '0'
|
139 |
|
140 | // determine whether or not to output exponential notation
|
141 | let str
|
142 | const rounded = value.toSignificantDigits(precision)
|
143 | const exp = rounded.e
|
144 | if (exp >= lowerExp && exp < upperExp) {
|
145 | // normal number notation
|
146 | str = rounded.toFixed()
|
147 | } else {
|
148 | // exponential notation
|
149 | str = toExponential(value, precision)
|
150 | }
|
151 |
|
152 | // remove trailing zeros after the decimal point
|
153 | return str.replace(/((\.\d*?)(0+))($|e)/, function () {
|
154 | const digits = arguments[2]
|
155 | const e = arguments[4]
|
156 | return (digits !== '.') ? digits + e : e
|
157 | })
|
158 |
|
159 | default:
|
160 | throw new Error('Unknown notation "' + notation + '". ' +
|
161 | 'Choose "auto", "exponential", or "fixed".')
|
162 | }
|
163 | }
|
164 |
|
165 | /**
|
166 | * Format a BigNumber in engineering notation. Like '1.23e+6', '2.3e+0', '3.500e-3'
|
167 | * @param {BigNumber | string} value
|
168 | * @param {number} [precision] Optional number of significant figures to return.
|
169 | */
|
170 | export function toEngineering (value, precision) {
|
171 | // find nearest lower multiple of 3 for exponent
|
172 | const e = value.e
|
173 | const newExp = e % 3 === 0 ? e : (e < 0 ? (e - 3) - (e % 3) : e - (e % 3))
|
174 |
|
175 | // find difference in exponents, and calculate the value without exponent
|
176 | const valueWithoutExp = value.mul(Math.pow(10, -newExp))
|
177 |
|
178 | let valueStr = valueWithoutExp.toPrecision(precision)
|
179 | if (valueStr.indexOf('e') !== -1) {
|
180 | valueStr = valueWithoutExp.toString()
|
181 | }
|
182 |
|
183 | return valueStr + 'e' + (e >= 0 ? '+' : '') + newExp.toString()
|
184 | }
|
185 |
|
186 | /**
|
187 | * Format a number in exponential notation. Like '1.23e+5', '2.3e+0', '3.500e-3'
|
188 | * @param {BigNumber} value
|
189 | * @param {number} [precision] Number of digits in formatted output.
|
190 | * If not provided, the maximum available digits
|
191 | * is used.
|
192 | * @returns {string} str
|
193 | */
|
194 | export function toExponential (value, precision) {
|
195 | if (precision !== undefined) {
|
196 | return value.toExponential(precision - 1) // Note the offset of one
|
197 | } else {
|
198 | return value.toExponential()
|
199 | }
|
200 | }
|
201 |
|
202 | /**
|
203 | * Format a number with fixed notation.
|
204 | * @param {BigNumber} value
|
205 | * @param {number} [precision=undefined] Optional number of decimals after the
|
206 | * decimal point. Undefined by default.
|
207 | */
|
208 | export function toFixed (value, precision) {
|
209 | return value.toFixed(precision)
|
210 | }
|