Repository: josdejong/mathjs
Pull request #525 (2015-12-25 20:09:00)

Realmode exponentiation
Here's an experiment with `realmode` option for exponentiation.

Example:
```js
console.log(math.eval('(-8)^(2/3)').toString());
math.config({realmode:true});
console.log(math.eval('(-8)^(2/3)').toString());
```

Output:
```
-1.9999999999999987 + 3.464101615137754i
3.9999999999999996
```

Let's try this out and see how it feels and make sure everything seems right with this. We can change the name of `realmode` too, or use an existing option like `predictable`, or whatever. When we are satisfied with it then I will add some notes to the docs before you merge the PR.


Patch:
diff --git a/HISTORY.md b/HISTORY.md
index 432dc206a..7517d20e7 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -4,8 +4,6 @@
 ## not yet released, version 2.6.0
 
 - Implemented (complex) units `VA` and `VAR`.
-- Implemented time units for weeks, months, years, decades, centuries, and 
-- millennia. Thanks @owenversteeg.
 - Implemented new notation `engineering` in function `math.format`. 
   Thanks @johnmarinelli.
 - Fixed #523: In some circumstances, matrix subset returned a scalar instead 
diff --git a/docs/reference/units.md b/docs/reference/units.md
index cf6cc0cb5..0d73a6f0e 100644
--- a/docs/reference/units.md
+++ b/docs/reference/units.md
@@ -14,7 +14,7 @@ Surface             | m2, sqin, sqft, sqyd, sqmi, sqrd, sqch, sqmil
 Volume              | m3, litre (l, L, lt, liter), cc, cuin, cuft, cuyd, teaspoon, tablespoon
 Liquid volume       | minim (min), fluiddram (fldr), fluidounce (floz), gill (gi), cup (cp), pint (pt), quart (qt), gallon (gal), beerbarrel (bbl), oilbarrel (obl), hogshead, drop (gtt)
 Angles              | rad (radian), deg (degree), grad (gradian), cycle, arcsec (arcsecond), arcmin (arcminute) 
-Time                | second (s), seconds, minute (minutes), hour (h, hours), day (days), week (weeks), month (months), year (years), decade (decades), century (centuries), millennium (millennia)
+Time                | second (s), seconds, minute (minutes), hour (h, hours), day (days)
 Frequency           | hertz (Hz)
 Mass                | gram(g), tonne, ton, grain (gr), dram(dr), ounce (oz), poundmass (lbm, lb, lbs), hundredweight (cwt), stick, stone
 Electric current    | ampere (A)
@@ -28,8 +28,6 @@ Pressure            | Pa, psi, atm
 Electricity and magnetism | ampere (A), coulomb (C), watt (W), volt (V), ohm, farad (F), weber (Wb), tesla (T), henry (H), siemens (S), electronvolt (eV)
 Binary              | bit (b), byte (B)
 
-Note: all time units are based on the Julian year, with one month being 1/12th of a Julian year, a year being one Julian year, a decade being 10 Julian years, a century being 100, and a millennium being 1000.
-
 Note that all relevant units can also be written in plural form, for example `5 meters` instead of `5 meter` or `10 seconds` instead of `10 second`. 
 
 Surface and volume units can alternatively be expressed in terms of length units raised to a power, for example `100 in^2` instead of `100 sqin`.
diff --git a/lib/function/arithmetic/pow.js b/lib/function/arithmetic/pow.js
index b8b190795..94f701cb1 100644
--- a/lib/function/arithmetic/pow.js
+++ b/lib/function/arithmetic/pow.js
@@ -10,6 +10,8 @@ function factory (type, config, load, typed) {
   var log = load(require('./log'));
   var multiply = load(require('./multiply'));
   var matrix = load(require('../../type/matrix/function/matrix'));
+  var fraction = load(require('../../type/fraction/function/fraction'));
+  var number = load(require('../../type/number'));
 
   /**
    * Calculates the power of x to y, `x ^ y`.
@@ -92,6 +94,27 @@ function factory (type, config, load, typed) {
    * @private
    */
   function _pow(x, y) {
+
+    // Alternatively could define a 'realmode' config option or something, but
+    // 'predictable' will work for now
+    if (config.predictable && !isInteger(y) && x < 0) {
+      // Check to see if y can be represented as a fraction
+      try {
+        var yFrac = fraction(y);
+        var yNum = number(yFrac);
+        if(y === yNum || Math.abs((y - yNum) / y) < 1e-14) {
+          if(yFrac.d % 2 === 1) {
+            return (yFrac.n % 2 === 0 ? 1 : -1) * Math.pow(-x, y);
+          }
+        }
+      }
+      catch (ex) {
+        // fraction() throws an error if y is Infinity, etc.
+      }
+
+      // Unable to express y as a fraction, so continue on
+    }
+
     if (isInteger(y) || x >= 0 || config.predictable) {
       return Math.pow(x, y);
     }
diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js
index e6c291c3e..2deea41f2 100644
--- a/lib/type/unit/Unit.js
+++ b/lib/type/unit/Unit.js
@@ -1954,48 +1954,6 @@ function factory (type, config, load, typed) {
       value: 86400,
       offset: 0
     },
-    week: {
-      name: 'week',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 7*86400,
-      offset: 0
-    },
-    month: {
-      name: 'month',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 2629800, //1/12th of Julian year
-      offset: 0
-    },
-    year: {
-      name: 'year',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 31557600, //Julian year
-      offset: 0
-    },
-    decade: {
-      name: 'year',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 315576000, //Julian decade
-      offset: 0
-    },
-    century: {
-      name: 'century',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 3155760000, //Julian century
-      offset: 0
-    },
-    millennium: {
-      name: 'millennium',
-      base: BASE_UNITS.TIME,
-      prefixes: PREFIXES.NONE,
-      value: 31557600000, //Julian millennium
-      offset: 0
-    },
 
     // Frequency
     hertz: {


Test patch:
diff --git a/test/function/arithmetic/pow.test.js b/test/function/arithmetic/pow.test.js
index fcd1ddda1..3b5b429dc 100644
--- a/test/function/arithmetic/pow.test.js
+++ b/test/function/arithmetic/pow.test.js
@@ -36,6 +36,30 @@ describe('pow', function() {
     assert(isNaN(res));
   });
 
+  it('should return a real-valued root if one exists with predictable:true', function() {
+    approx.equal(mathPredictable.pow(-8, 1/3), -2);
+    approx.equal(mathPredictable.pow(-8, 2/3), 4);
+    approx.equal(mathPredictable.pow(-8, 3/3), -8);
+    approx.equal(mathPredictable.pow(-8, 4/3), 16);
+    approx.equal(mathPredictable.pow(-8, 5/3), -32);
+    approx.equal(mathPredictable.pow(-8, -5/3), -0.03125);
+    approx.equal(mathPredictable.pow(-1, 2/3), 1);
+    approx.equal(mathPredictable.pow(-1, 50/99), 1);
+    approx.equal(mathPredictable.pow(-1, 49/99), -1);
+    approx.equal(mathPredictable.pow(-17, 29/137), -1.8216292479175);
+    approx.equal(mathPredictable.pow(-1, 0), 1);
+    approx.equal(mathPredictable.pow(-1, 0.2), -1);
+    approx.equal(mathPredictable.pow(-1, 1), -1);
+
+    approx.equal(mathPredictable.pow(4, 2), 16);
+    approx.equal(mathPredictable.pow(4, 0.5), 2);
+    approx.equal(mathPredictable.pow(-4, 2), 16);
+
+    assert(isNaN(mathPredictable.pow(-1, 49/100)));
+    assert(isNaN(mathPredictable.pow(-17, 29/138)));
+    assert(isNaN(mathPredictable.pow(-17, 3.14159265358979323)));
+  });
+
   it('should exponentiate booleans to the given power', function() {
     assert.equal(pow(true, true), 1);
     assert.equal(pow(true, false), 1);
@@ -171,8 +195,6 @@ describe('pow', function() {
 	});
 
   it('should throw an error when doing number ^ unit', function() {
-    // This is supported now --ericman314
-    //assert.throws(function () {pow(unit('5cm'), 2)});
     assert.throws(function () {pow(2, unit('5cm'))});
   });