formatter.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { isInteger } from '../number.js';
  2. /**
  3. * Formats a BigNumber in a given base
  4. * @param {BigNumber} n
  5. * @param {number} base
  6. * @param {number} size
  7. * @returns {string}
  8. */
  9. function formatBigNumberToBase(n, base, size) {
  10. var BigNumberCtor = n.constructor;
  11. var big2 = new BigNumberCtor(2);
  12. var suffix = '';
  13. if (size) {
  14. if (size < 1) {
  15. throw new Error('size must be in greater than 0');
  16. }
  17. if (!isInteger(size)) {
  18. throw new Error('size must be an integer');
  19. }
  20. if (n.greaterThan(big2.pow(size - 1).sub(1)) || n.lessThan(big2.pow(size - 1).mul(-1))) {
  21. throw new Error("Value must be in range [-2^".concat(size - 1, ", 2^").concat(size - 1, "-1]"));
  22. }
  23. if (!n.isInteger()) {
  24. throw new Error('Value must be an integer');
  25. }
  26. if (n.lessThan(0)) {
  27. n = n.add(big2.pow(size));
  28. }
  29. suffix = "i".concat(size);
  30. }
  31. switch (base) {
  32. case 2:
  33. return "".concat(n.toBinary()).concat(suffix);
  34. case 8:
  35. return "".concat(n.toOctal()).concat(suffix);
  36. case 16:
  37. return "".concat(n.toHexadecimal()).concat(suffix);
  38. default:
  39. throw new Error("Base ".concat(base, " not supported "));
  40. }
  41. }
  42. /**
  43. * Convert a BigNumber to a formatted string representation.
  44. *
  45. * Syntax:
  46. *
  47. * format(value)
  48. * format(value, options)
  49. * format(value, precision)
  50. * format(value, fn)
  51. *
  52. * Where:
  53. *
  54. * {number} value The value to be formatted
  55. * {Object} options An object with formatting options. Available options:
  56. * {string} notation
  57. * Number notation. Choose from:
  58. * 'fixed' Always use regular number notation.
  59. * For example '123.40' and '14000000'
  60. * 'exponential' Always use exponential notation.
  61. * For example '1.234e+2' and '1.4e+7'
  62. * 'auto' (default) Regular number notation for numbers
  63. * having an absolute value between
  64. * `lower` and `upper` bounds, and uses
  65. * exponential notation elsewhere.
  66. * Lower bound is included, upper bound
  67. * is excluded.
  68. * For example '123.4' and '1.4e7'.
  69. * 'bin', 'oct, or
  70. * 'hex' Format the number using binary, octal,
  71. * or hexadecimal notation.
  72. * For example '0b1101' and '0x10fe'.
  73. * {number} wordSize The word size in bits to use for formatting
  74. * in binary, octal, or hexadecimal notation.
  75. * To be used only with 'bin', 'oct', or 'hex'
  76. * values for 'notation' option. When this option
  77. * is defined the value is formatted as a signed
  78. * twos complement integer of the given word size
  79. * and the size suffix is appended to the output.
  80. * For example
  81. * format(-1, {notation: 'hex', wordSize: 8}) === '0xffi8'.
  82. * Default value is undefined.
  83. * {number} precision A number between 0 and 16 to round
  84. * the digits of the number.
  85. * In case of notations 'exponential',
  86. * 'engineering', and 'auto',
  87. * `precision` defines the total
  88. * number of significant digits returned.
  89. * In case of notation 'fixed',
  90. * `precision` defines the number of
  91. * significant digits after the decimal
  92. * point.
  93. * `precision` is undefined by default.
  94. * {number} lowerExp Exponent determining the lower boundary
  95. * for formatting a value with an exponent
  96. * when `notation='auto`.
  97. * Default value is `-3`.
  98. * {number} upperExp Exponent determining the upper boundary
  99. * for formatting a value with an exponent
  100. * when `notation='auto`.
  101. * Default value is `5`.
  102. * {Function} fn A custom formatting function. Can be used to override the
  103. * built-in notations. Function `fn` is called with `value` as
  104. * parameter and must return a string. Is useful for example to
  105. * format all values inside a matrix in a particular way.
  106. *
  107. * Examples:
  108. *
  109. * format(6.4) // '6.4'
  110. * format(1240000) // '1.24e6'
  111. * format(1/3) // '0.3333333333333333'
  112. * format(1/3, 3) // '0.333'
  113. * format(21385, 2) // '21000'
  114. * format(12e8, {notation: 'fixed'}) // returns '1200000000'
  115. * format(2.3, {notation: 'fixed', precision: 4}) // returns '2.3000'
  116. * format(52.8, {notation: 'exponential'}) // returns '5.28e+1'
  117. * format(12400, {notation: 'engineering'}) // returns '12.400e+3'
  118. *
  119. * @param {BigNumber} value
  120. * @param {Object | Function | number} [options]
  121. * @return {string} str The formatted value
  122. */
  123. export function format(value, options) {
  124. if (typeof options === 'function') {
  125. // handle format(value, fn)
  126. return options(value);
  127. }
  128. // handle special cases
  129. if (!value.isFinite()) {
  130. return value.isNaN() ? 'NaN' : value.gt(0) ? 'Infinity' : '-Infinity';
  131. }
  132. // default values for options
  133. var notation = 'auto';
  134. var precision;
  135. var wordSize;
  136. if (options !== undefined) {
  137. // determine notation from options
  138. if (options.notation) {
  139. notation = options.notation;
  140. }
  141. // determine precision from options
  142. if (typeof options === 'number') {
  143. precision = options;
  144. } else if (options.precision) {
  145. precision = options.precision;
  146. }
  147. if (options.wordSize) {
  148. wordSize = options.wordSize;
  149. if (typeof wordSize !== 'number') {
  150. throw new Error('Option "wordSize" must be a number');
  151. }
  152. }
  153. }
  154. // handle the various notations
  155. switch (notation) {
  156. case 'fixed':
  157. return toFixed(value, precision);
  158. case 'exponential':
  159. return toExponential(value, precision);
  160. case 'engineering':
  161. return toEngineering(value, precision);
  162. case 'bin':
  163. return formatBigNumberToBase(value, 2, wordSize);
  164. case 'oct':
  165. return formatBigNumberToBase(value, 8, wordSize);
  166. case 'hex':
  167. return formatBigNumberToBase(value, 16, wordSize);
  168. case 'auto':
  169. {
  170. // determine lower and upper bound for exponential notation.
  171. // TODO: implement support for upper and lower to be BigNumbers themselves
  172. var lowerExp = options && options.lowerExp !== undefined ? options.lowerExp : -3;
  173. var upperExp = options && options.upperExp !== undefined ? options.upperExp : 5;
  174. // handle special case zero
  175. if (value.isZero()) return '0';
  176. // determine whether or not to output exponential notation
  177. var str;
  178. var rounded = value.toSignificantDigits(precision);
  179. var exp = rounded.e;
  180. if (exp >= lowerExp && exp < upperExp) {
  181. // normal number notation
  182. str = rounded.toFixed();
  183. } else {
  184. // exponential notation
  185. str = toExponential(value, precision);
  186. }
  187. // remove trailing zeros after the decimal point
  188. return str.replace(/((\.\d*?)(0+))($|e)/, function () {
  189. var digits = arguments[2];
  190. var e = arguments[4];
  191. return digits !== '.' ? digits + e : e;
  192. });
  193. }
  194. default:
  195. throw new Error('Unknown notation "' + notation + '". ' + 'Choose "auto", "exponential", "fixed", "bin", "oct", or "hex.');
  196. }
  197. }
  198. /**
  199. * Format a BigNumber in engineering notation. Like '1.23e+6', '2.3e+0', '3.500e-3'
  200. * @param {BigNumber | string} value
  201. * @param {number} [precision] Optional number of significant figures to return.
  202. */
  203. export function toEngineering(value, precision) {
  204. // find nearest lower multiple of 3 for exponent
  205. var e = value.e;
  206. var newExp = e % 3 === 0 ? e : e < 0 ? e - 3 - e % 3 : e - e % 3;
  207. // find difference in exponents, and calculate the value without exponent
  208. var valueWithoutExp = value.mul(Math.pow(10, -newExp));
  209. var valueStr = valueWithoutExp.toPrecision(precision);
  210. if (valueStr.indexOf('e') !== -1) {
  211. valueStr = valueWithoutExp.toString();
  212. }
  213. return valueStr + 'e' + (e >= 0 ? '+' : '') + newExp.toString();
  214. }
  215. /**
  216. * Format a number in exponential notation. Like '1.23e+5', '2.3e+0', '3.500e-3'
  217. * @param {BigNumber} value
  218. * @param {number} [precision] Number of digits in formatted output.
  219. * If not provided, the maximum available digits
  220. * is used.
  221. * @returns {string} str
  222. */
  223. export function toExponential(value, precision) {
  224. if (precision !== undefined) {
  225. return value.toExponential(precision - 1); // Note the offset of one
  226. } else {
  227. return value.toExponential();
  228. }
  229. }
  230. /**
  231. * Format a number with fixed notation.
  232. * @param {BigNumber} value
  233. * @param {number} [precision=undefined] Optional number of decimals after the
  234. * decimal point. Undefined by default.
  235. */
  236. export function toFixed(value, precision) {
  237. return value.toFixed(precision);
  238. }