mathjs/lib/util/NumberFormatter.js

172 lines
4.9 KiB
JavaScript

'use strict';
/**
* Format a number using methods toPrecision, toFixed, toExponential.
* @param {number | string} value
* @constructor
*/
function NumberFormatter (value) {
// parse the input value
var match = String(value).toLowerCase().match(/^0*?(-?)(\d+\.?\d*)(e([+-]?\d+))?$/);
if (!match) {
throw new SyntaxError('Invalid number');
}
var sign = match[1];
var coefficients = match[2];
var exponent = parseFloat(match[4] || '0');
var dot = coefficients.indexOf('.');
exponent += (dot !== -1) ? (dot - 1) : (coefficients.length - 1);
this.sign = sign;
this.coefficients = coefficients
.replace('.', '') // remove the dot (must be removed before removing leading zeros)
.replace(/^0*/, function (zeros) {
// remove leading zeros, add their count to the exponent
exponent -= zeros.length;
return '';
})
.replace(/0*$/, '') // remove trailing zeros
.split('');
if (this.coefficients.length === 0) {
this.coefficients.push(0);
exponent++;
}
this.exponent = exponent;
}
/**
* Format a number with fixed notation.
* @param {Number} [precision=0] Optional number of decimals after the
* decimal point. Zero by default.
*/
NumberFormatter.prototype.toFixed = function (precision) {
if (precision) {
var dot = this.exponent > 0 ? this.exponent : 0;
var all = init(-this.exponent, 0).concat(this.coefficients);
var coefficients = round(all, dot + precision + 1);
coefficients.splice(dot + 1, 0, '.');
return this.sign + coefficients.join('');
}
else {
// TODO: make the || '0' redundant
return this.sign + round(this.coefficients, this.exponent + 1).join('') || '0';
}
};
/**
* Format a number in exponential notation. Like '1.23e+5', '2.3e+0', '3.500e-3'
* @param {Number} [precision] Number of digits in formatted output.
* If not provided, the maximum available digits
* is used.
*/
NumberFormatter.prototype.toExponential = function (precision) {
var coefficients = precision
? round(this.coefficients, precision)
: this.coefficients.slice(0);
var first = coefficients.shift();
return this.sign +
first +
(coefficients.length > 0 ? ('.' + coefficients.join('')) : '') +
'e' + (this.exponent >= 0 ? '+' : '') + this.exponent;
};
/**
* Format a number with a certain precision
* @param {Number} [precision=undefined] Optional number of digits.
* @param {{lower: number | undefined, upper: number | undefined}} [options]
* By default:
* lower = 1e-3 (excl)
* upper = 1e+5 (incl)
* @return {string}
*/
NumberFormatter.prototype.toPrecision = function(precision, options) {
// determine lower and upper bound for exponential notation.
var lower = (options && options.lower !== undefined) ? options.lower : 1e-3;
var upper = (options && options.upper !== undefined) ? options.upper : 1e+5;
var abs = Math.abs(Math.pow(10, this.exponent));
if (abs < lower || abs >= upper) {
// exponential notation
return this.toExponential(precision);
}
else {
var all = init(-this.exponent, 0).concat(this.coefficients);
var coefficients = precision
? round(all, precision + (this.exponent < 0 ? -this.exponent : 0))
: all;
// append trailing zeros
var trailing = init(this.exponent - coefficients.length + 1, 0);
coefficients = coefficients.concat(trailing);
var dot = this.exponent > 0 ? this.exponent : 0;
if (dot < coefficients.length - 1) {
coefficients.splice(dot + 1, 0, '.');
}
return this.sign + coefficients.join('');
}
};
/**
* Round an array with coefficients. For example:
*
* round([2,3,4,5,6], 2) returns '23'
* round([2,3,4,5,6], 4) returns '2346'
* round([2,3], 4) returns '2300'
*
* @param {number[]} coefficients
* @param {number} count
* @return {number[]} Returns array with rounded coefficients
*/
function round(coefficients, count) {
// TODO: simplify this method, write in a more compact way
var rounded = coefficients.slice(0, count);
if (coefficients[count] >= 5) {
if (count === 0) {
rounded.unshift(0);
count++;
}
rounded[count - 1]++;
var i = count - 1;
while (i > 0) {
if (rounded[i] === 10) {
rounded[i] = 0;
if (i === 0) {
rounded.unshift(0);
i++;
}
rounded[i - 1]++;
}
i--;
}
}
return rounded.concat(init(count - rounded.length, 0));
}
/**
* Initialize an array with a certain length and initialize with values.
* @param {number} length
* @param {number} value
* @return {Array}
*/
function init(length, value) {
var arr = [];
for (var i = 0; i < length; i++) {
arr.push(value);
}
return arr;
}
module.exports = NumberFormatter;