Repository: josdejong/mathjs
Pull request #519 (2015-12-21 00:35:01)

Issue457
(https://github.com/josdejong/mathjs/issues/457)

First draft for the engineering notation feature.  The algorithm is a bit ugly and I want to clean it up, but I wanted to make sure that everything else was good.  I think I may have written too many tests; let me know if you want me to remove some.


Patch:
diff --git a/lib/utils/NumberFormatter.js b/lib/utils/NumberFormatter.js
index 6f337ae16..833902e6d 100644
--- a/lib/utils/NumberFormatter.js
+++ b/lib/utils/NumberFormatter.js
@@ -41,6 +41,47 @@ function NumberFormatter (value) {
   this.exponent = exponent;
 }
 
+
+/**
+ * Format a number with engineering notation.
+ * @param {number} [precision=0]        Optional number of decimals after the
+ *                                      decimal point. Zero by default.
+ */
+NumberFormatter.prototype.toEngineering = function(precision) {
+  var rounded = this.roundDigits(precision);
+
+  var e = rounded.exponent;
+  var c = rounded.coefficients;
+
+  // find nearest lower multiple of 3 for exponent
+  var newExp = e % 3 === 0 ? e : (e < 0 ? (e - 3) - (e % 3) : e - (e % 3));
+
+  // concatenate coefficients with necessary zeros
+  var significandsDiff = e >= 0 ? e : Math.abs(newExp);
+
+  // add zeros if necessary (for ex: 1e+8)
+  if (c.length - 1 < significandsDiff) c = c.concat(zeros(significandsDiff - (c.length - 1)));
+
+  // find difference in exponents
+  var expDiff = Math.abs(e - newExp);
+
+  var decimalIdx = 1;
+  var str = '';
+
+  // push decimal index over by expDiff times
+  while (--expDiff >= 0) decimalIdx++;
+
+  // if all coefficient values are zero after the decimal point, don't add a decimal value. 
+  // otherwise concat with the rest of the coefficients
+  var decimals = c.slice(decimalIdx).join('');
+  var decimalVal = decimals.match(/[1-9]/) ? ('.' + decimals) : '';
+
+  str = c.slice(0, decimalIdx).join('') + decimalVal;
+
+  str += 'e' + (e >= 0 ? '+' : '') + newExp.toString();
+  return rounded.sign + str;
+}
+
 /**
  * Format a number with fixed notation.
  * @param {number} [precision=0]        Optional number of decimals after the
diff --git a/lib/utils/number.js b/lib/utils/number.js
index d57cdcd21..46845553d 100644
--- a/lib/utils/number.js
+++ b/lib/utils/number.js
@@ -60,6 +60,8 @@ exports.sign = function(x) {
  *                                          For example '123.40' and '14000000'
  *                         'exponential'    Always use exponential notation.
  *                                          For example '1.234e+2' and '1.4e+7'
+ *                         'engineering'    Always use engineering notation.
+ *                                          For example '123.4e+0 and 14.0e+6'
  *                         'auto' (default) Regular number notation for numbers
  *                                          having an absolute value between
  *                                          `lower` and `upper` bounds, and uses
@@ -99,6 +101,7 @@ exports.sign = function(x) {
  *    format(12.071, {notation: 'fixed'});                // '12'
  *    format(2.3,    {notation: 'fixed', precision: 2});  // '2.30'
  *    format(52.8,   {notation: 'exponential'});          // '5.28e+1'
+ *    format(12345678, {notation: 'engineering'});        // '12.345678e+6'
  *
  * @param {number} value
  * @param {Object | Function | number} [options]
@@ -148,6 +151,9 @@ exports.format = function(value, options) {
     case 'exponential':
       return exports.toExponential(value, precision);
 
+    case 'engineering':
+      return exports.toEngineering(value, precision);
+
     case 'auto':
       return exports
           .toPrecision(value, precision, options && options.exponential)
@@ -177,6 +183,18 @@ exports.toExponential = function(value, precision) {
   return new NumberFormatter(value).toExponential(precision);
 };
 
+/**
+ * Format a number in engineering notation. Like '1.23e+6', '2.3e+0', '3.500e-3'
+ * @param {number} value
+ * @param {number} [precision]  Number of digits in formatted output.
+ *                              If not provided, the maximum available digits
+ *                              is used.
+ * @returns {string} str
+ */
+exports.toEngineering = function(value, precision) {
+  return new NumberFormatter(value).toEngineering(precision);
+};
+
 /**
  * Format a number with fixed notation.
  * @param {number} value


Test patch:
diff --git a/test/function/utils/format.test.js b/test/function/utils/format.test.js
index df3101651..4aef38865 100644
--- a/test/function/utils/format.test.js
+++ b/test/function/utils/format.test.js
@@ -60,6 +60,51 @@ describe('format', function() {
 
   });
 
+  describe('engineering notation', function () {
+    it('should format positive single digit to engineering notation', function() {
+      assert.equal(math.format(3, { notation: 'engineering' }), '3e+0');
+    });
+    it('should format positive two digits to engineering notation', function() {
+      assert.equal(math.format(30, { notation: 'engineering' }), '30e+0');
+    });
+    it('should format positive three digits to engineering notation', function() {
+      assert.equal(math.format(300, { notation: 'engineering' }), '300e+0');
+    });
+    it('should format positive four digits to engineering notation', function() {
+      assert.equal(math.format(3000, { notation: 'engineering' }), '3e+3');
+    });
+    it('should format positive uneven four digits to engineering notation', function() {
+      assert.equal(math.format(3001, { notation: 'engineering' }), '3.001e+3');
+    });
+    it('should format positive uneven ten digits to engineering notation', function() {
+      assert.equal(math.format(3741293481, { notation: 'engineering' }), '3.741293481e+9');
+    });
+    it('should format negative uneven ten digits to engineering notation', function() {
+      assert.equal(math.format(-3741293481, { notation: 'engineering' }), '-3.741293481e+9');
+    });
+    it('should format positive single digit floating point numbers to engineering notation', function() {
+      assert.equal(math.format(.1, { notation: 'engineering' }), '100e-3');
+    });
+    it('should format positive two digit floating point numbers to engineering notation', function() {
+      assert.equal(math.format(.01, { notation: 'engineering' }), '10e-3');
+    });
+    it('should format positive three digit floating point numbers to engineering notation', function() {
+      assert.equal(math.format(.003, { notation: 'engineering' }), '3e-3');
+    });
+    it('should format positive repeating three digit floating point numbers to engineering notation with precision', function() {
+      assert.equal(math.format(1/3, { precision: 3, notation: 'engineering' }), '333e-3');
+    });
+    it('should format positive seven digit floating point numbers to engineering notation', function() {
+      assert.equal(math.format(.1234567, { notation: 'engineering' }), '123.4567e-3');
+    });
+    it('should format negative single digit floating point numbers to engineering notation', function() {
+      assert.equal(math.format(-.1, { notation: 'engineering' }), '-100e-3');
+    });
+    it('should format positive floating point number to engineering notation', function() {
+      assert.equal(math.format(13308.0333333333, { precision: 11, notation: 'engineering' }), '13.308033333e+3');
+    });
+  });
+
   describe('bignumber', function () {
     var bigmath = math.create({precision: 20}); // ensure the precision is 20 digits