Some more fixes and improvements in NumberFormatter

This commit is contained in:
jos 2015-04-07 22:16:47 +02:00
parent 2cdeb0d49b
commit 5dfa79196e
4 changed files with 118 additions and 61 deletions

View File

@ -3,7 +3,7 @@
## not-yet-released, version 1.5.1
- Fixed #316: a bug in rounding values when formatting.
- Fixed #317: a bug in formatting negative values.
- Fixed #317, #319: a bug in formatting negative values.
## 2015-03-28, version 1.5.0

View File

@ -47,18 +47,28 @@ function NumberFormatter (value) {
* 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, '.');
var rounded = this.roundDigits(this.exponent + 1 + (precision || 0));
var c = rounded.coefficients;
var p = rounded.exponent + 1; // exponent may have changed
return this.sign + coefficients.join('');
// append zeros if needed
var pp = p + (precision || 0);
if (c.length < pp) {
c = c.concat(zeros(pp - c.length));
}
else {
// TODO: make the || '0' redundant
return this.sign + round(this.coefficients, this.exponent + 1).join('') || '0';
// prepend zeros if needed
if (p < 0) {
c = zeros(-p + 1).concat(c);
p = 1;
}
// insert a dot if needed
if (precision) {
c.splice(p, 0, (p === 0) ? '0.' : '.');
}
return this.sign + c.join('');
};
/**
@ -68,15 +78,20 @@ NumberFormatter.prototype.toFixed = function (precision) {
* is used.
*/
NumberFormatter.prototype.toExponential = function (precision) {
var coefficients = precision
? round(this.coefficients, precision)
: this.coefficients.slice(0);
// round if needed, else create a clone
var rounded = precision ? this.roundDigits(precision) : this.clone();
var c = rounded.coefficients;
var e = rounded.exponent;
var first = coefficients.shift();
return this.sign +
first +
(coefficients.length > 0 ? ('.' + coefficients.join('')) : '') +
'e' + (this.exponent >= 0 ? '+' : '') + this.exponent;
// append zeros if needed
if (c.length < precision) {
c = c.concat(zeros(precision - c.length));
}
// format as `C.CCCe+EEE` or `C.CCCe-EEE`
var first = c.shift();
return this.sign + first + (c.length > 0 ? ('.' + c.join('')) : '') +
'e' + (e >= 0 ? '+' : '') + e;
};
/**
@ -99,74 +114,92 @@ NumberFormatter.prototype.toPrecision = function(precision, options) {
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;
var rounded = precision ? this.roundDigits(precision) : this.clone();
var c = rounded.coefficients;
var e = rounded.exponent;
// 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, '.');
if (c.length < precision) {
c = c.concat(zeros(precision - c.length));
}
return this.sign + coefficients.join('');
// append trailing zeros
// TODO: simplify the next statement
c = c.concat(zeros(e - c.length + 1 +
(c.length < precision ? precision - c.length : 0)));
// prepend zeros
c = zeros(-e).concat(c);
var dot = e > 0 ? e : 0;
if (dot < c.length - 1) {
c.splice(dot + 1, 0, '.');
}
return this.sign + c.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
* Crete a clone of the NumberFormatter
* @return {NumberFormatter} Returns a clone of the NumberFormatter
*/
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]++;
NumberFormatter.prototype.clone = function () {
var clone = new NumberFormatter('0');
clone.sign = this.sign;
clone.coefficients = this.coefficients.slice(0);
clone.exponent = this.exponent;
return clone;
};
var i = count - 1;
while (i > 0) {
if (rounded[i] === 10) {
rounded[i] = 0;
/**
* Round the number of digits of a number *
* @param {number} precision A positive integer
* @return {NumberFormatter} Returns a new NumberFormatter with the rounded
* digits
*/
NumberFormatter.prototype.roundDigits = function (precision) {
var rounded = this.clone();
var c = rounded.coefficients;
// prepend zeros if needed
while (precision <= 0) {
c.unshift(0);
rounded.exponent++;
precision++;
}
if (c.length > precision) {
var removed = c.splice(precision);
if (removed[0] >= 5) {
var i = precision - 1;
c[i]++;
while (c[i] === 10) {
c.pop();
if (i === 0) {
rounded.unshift(0);
c.unshift(0);
rounded.exponent++;
i++;
}
rounded[i - 1]++;
i--;
c[i]++;
}
i--;
}
}
return rounded.concat(init(count - rounded.length, 0));
}
return rounded;
};
/**
* Initialize an array with a certain length and initialize with values.
* Create an array filled with zeros.
* @param {number} length
* @param {number} value
* @return {Array}
*/
function init(length, value) {
function zeros(length) {
var arr = [];
for (var i = 0; i < length; i++) {
arr.push(value);
arr.push(0);
}
return arr;
}

View File

@ -28,6 +28,21 @@ describe('NumberFormatter', function() {
assert.deepEqual(new NumberFormatter('2.3e-3'), {sign: '', coefficients: [2, 3], exponent: -3});
assert.deepEqual(new NumberFormatter('23e-3'), {sign: '', coefficients: [2, 3], exponent: -2});
assert.deepEqual(new NumberFormatter('-23e-3'), {sign: '-', coefficients: [2, 3], exponent: -2});
assert.deepEqual(new NumberFormatter('99.99'), {sign: '', coefficients: [9,9,9,9], exponent: 1});
});
it('should clone a NumberFormatter', function () {
var a = new NumberFormatter(2.3);
var clone = a.clone();
assert.deepEqual(clone, a);
assert.notStrictEqual(clone, a);
});
it('should round a NumberFormatter', function () {
assert.deepEqual(new NumberFormatter(123456).roundDigits(3), new NumberFormatter(123000));
assert.deepEqual(new NumberFormatter(123456).roundDigits(4), new NumberFormatter(123500));
assert.deepEqual(new NumberFormatter(0.00555).roundDigits(2), new NumberFormatter(0.0056));
assert.deepEqual(new NumberFormatter(99.99).roundDigits(2), new NumberFormatter(100));
});
it('should format a number with toFixed', function () {
@ -58,6 +73,8 @@ describe('NumberFormatter', function() {
it('should format a number with toExponential', function () {
assert.strictEqual(new NumberFormatter(0).toExponential(), '0e+0');
assert.strictEqual(new NumberFormatter(1).toExponential(), '1e+0');
assert.strictEqual(new NumberFormatter(1000).toExponential(), '1e+3');
assert.strictEqual(new NumberFormatter(2300).toExponential(), '2.3e+3');
assert.strictEqual(new NumberFormatter(3.568).toExponential(), '3.568e+0');
assert.strictEqual(new NumberFormatter(0.00123).toExponential(), '1.23e-3');
@ -67,6 +84,7 @@ describe('NumberFormatter', function() {
assert.strictEqual(new NumberFormatter(0).toExponential(2), '0.0e+0');
assert.strictEqual(new NumberFormatter(1234).toExponential(2), '1.2e+3');
assert.strictEqual(new NumberFormatter(1234).toExponential(6), '1.23400e+3');
assert.strictEqual(new NumberFormatter(9999).toExponential(2), '1.0e+4');
});
it('should format a number with toPrecision', function () {
@ -79,9 +97,14 @@ describe('NumberFormatter', function() {
assert.strictEqual(new NumberFormatter(2300).toPrecision(6), '2300.00');
assert.strictEqual(new NumberFormatter(1234.5678).toPrecision(6), '1234.57');
assert.strictEqual(new NumberFormatter(1234.5678).toPrecision(2), '1200');
assert.strictEqual(new NumberFormatter(1234).toPrecision(2), '1200');
assert.strictEqual(new NumberFormatter(0.004).toPrecision(3), '0.00400');
assert.strictEqual(new NumberFormatter(0.00123456).toPrecision(5), '0.0012346');
assert.strictEqual(new NumberFormatter(999).toPrecision(2), '1000');
assert.strictEqual(new NumberFormatter(9990).toPrecision(2), '10000');
assert.strictEqual(new NumberFormatter(99999).toPrecision(2), '100000');
assert.strictEqual(new NumberFormatter(999e7).toPrecision(2), '1.0e+10');
});
it('should should throw an error on invalid input', function () {

View File

@ -128,6 +128,7 @@ describe('number', function() {
assert.equal(number.format(123.456, options), '123');
assert.equal(number.format(123.7, options), '124');
assert.equal(number.format(-123.7, options), '-124');
assert.equal(number.format(-66, options), '-66');
assert.equal(number.format(0.123456, options), '0');
assert.equal(number.format(123456789, options), '123456789');