Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified the handling of zero and infinity to map to the Riemann sphere. #17

Merged
merged 4 commits into from
Jan 28, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 99 additions & 20 deletions complex.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,16 @@

var z = new Complex(a, b);

// Infinity + Infinity = NaN
if (this.isInfinite() && z.isInfinite()) {
return Complex.NAN;
}

// Infinity + z = Infinity { where z != Infinity }
if (this.isInfinite() || z.isInfinite()) {
return Complex.INFINITY;
}

return new Complex(
this['re'] + z['re'],
this['im'] + z['im']);
Expand All @@ -291,6 +301,16 @@

var z = new Complex(a, b);

// Infinity - Infinity = NaN
if (this.isInfinite() && z.isInfinite()) {
return Complex.NAN;
}

// Infinity - z = Infinity { where z != Infinity }
if (this.isInfinite() || z.isInfinite()) {
return Complex.INFINITY;
}

return new Complex(
this['re'] - z['re'],
this['im'] - z['im']);
Expand All @@ -305,7 +325,17 @@

var z = new Complex(a, b);

// Besides the addition/subtraction, this helps having a solution for real Infinity
// Infinity * 0 = NaN
if ((this.isInfinite() && z.isZero()) || (this.isZero() && z.isInfinite())) {
return Complex.NAN;
}

// Infinity * z = Infinity { where z != 0 }
if (this.isInfinite() || z.isInfinite()) {
return Complex.INFINITY;
}

// Short circuit for real values
if (z['im'] === 0 && this['im'] === 0) {
return new Complex(this['re'] * z['re'], 0);
}
Expand All @@ -324,6 +354,21 @@

var z = new Complex(a, b);

// 0 / 0 = NaN and Infinity / Infinity = NaN
if ((this.isZero() && z.isZero()) || (this.isInfinite() && z.isInfinite())) {
return Complex.NAN;
}

// Infinity / 0 = Infinity
if (this.isInfinite() || z.isZero()) {
return Complex.INFINITY;
}

// 0 / Infinity = 0
if (this.isZero() || z.isInfinite()) {
return Complex.ZERO;
}

a = this['re'];
b = this['im'];

Expand All @@ -332,15 +377,8 @@
var t, x;

if (0 === d) {
if (0 === c) {
// Divisor is zero
return new Complex(
(a !== 0) ? (a / 0) : 0,
(b !== 0) ? (b / 0) : 0);
} else {
// Divisor is real
return new Complex(a / c, b / c);
}
// Divisor is real
return new Complex(a / c, b / c);
}

if (Math.abs(c) < Math.abs(d)) {
Expand Down Expand Up @@ -375,8 +413,8 @@
a = this['re'];
b = this['im'];

if (a === 0 && b === 0) {
return Complex['ZERO'];
if (z.isZero()) {
return Complex['ONE'];
}

// If the exponent is real
Expand Down Expand Up @@ -1036,8 +1074,8 @@
var a = this['re'];
var b = this['im'];

if (a === 0 && b === 0) {
return new Complex(Infinity, 0);
if (this.isZero()) {
return Complex.INFINITY;
}

var d = a * a + b * b;
Expand All @@ -1057,14 +1095,21 @@
*/
'inverse': function() {

// 1 / 0 = Infinity and 1 / Infinity = 0
if (this.isZero()) {
return Complex.INFINITY;
}

if (this.isInfinite()) {
return Complex.ZERO;
}

var a = this['re'];
var b = this['im'];

var d = a * a + b * b;

return new Complex(
a !== 0 ? a / d : 0,
b !== 0 ?-b / d : 0);
return new Complex(a / d, -b / d);
},

/**
Expand Down Expand Up @@ -1163,10 +1208,18 @@
var b = this['im'];
var ret = '';

if (isNaN(a) || isNaN(b)) {
if (this.isNaN()) {
return 'NaN';
}

if (this.isZero()) {
return '0';
}

if (this.isInfinite()) {
return 'Infinity';
}

if (a !== 0) {
ret+= a;
}
Expand Down Expand Up @@ -1217,7 +1270,7 @@
},

/**
* Checks if the given complex number is not a number
* Determines whether a complex number is not on the Riemann sphere.
*
* @returns {boolean}
*/
Expand All @@ -1226,12 +1279,36 @@
},

/**
* Checks if the given complex number is finite
* Determines whether or not a complex number is at the zero pole of the
* Riemann sphere.
*
* @returns {boolean}
*/
'isZero': function() {
return (
(this['re'] === 0 || this['re'] === -0) &&
(this['im'] === 0 || this['im'] === -0)
);
},

/**
* Determines whether a complex number is not at the infinity pole of the
* Riemann sphere.
*
* @returns {boolean}
*/
'isFinite': function() {
return isFinite(this['re']) && isFinite(this['im']);
},

/**
* Determines whether or not a complex number is at the infinity pole of the
* Riemann sphere.
*
* @returns {boolean}
*/
'isInfinite': function() {
return !(this.isNaN() || this.isFinite());
}
};

Expand All @@ -1240,6 +1317,8 @@
Complex['I'] = new Complex(0, 1);
Complex['PI'] = new Complex(Math.PI, 0);
Complex['E'] = new Complex(Math.E, 0);
Complex['INFINITY'] = new Complex(Infinity, Infinity);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer calling this ComplexInfinity or simliar to make it clear that this is different from the real infinity that people are used to in JavaScript

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the same could be said for the NAN. But since they are all CAPS and prefixed by the type (Complex.INFINITY and Complex.NAN) I am not sure the clarification is necessary. Doesn't matter to me though...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if ComplexInfinity is more unambiguous, I like Complex.INFINITY and Complex.NAN better, since you would normally use it exactly as "Complex.INFINITY" and so it states already what ComplexInfinity wants to express.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the question is how strongly do you feel about having the uppercase names. People are used to writing NaN rather than NAN so the upper case version may be confusing.

Complex['NAN'] = new Complex(NaN, NaN);
Complex['EPSILON'] = 1e-16;

if (typeof define === 'function' && define['amd']) {
Expand Down
80 changes: 73 additions & 7 deletions tests/complex.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,36 @@ var tests = [{
}, {
set: "2.3",
expect: "2.3"
}, {
set: "0",
expect: "0"
}, {
set: "-0",
expect: "0"
}, {
set: {re: -0, im: 0},
expect: "0"
}, {
set: {re: 0, im: -0},
expect: "0"
}, {
set: Infinity,
expect: "Infinity"
}, {
set: -Infinity,
expect: "Infinity"
}, {
set: {re: Infinity, im: 0},
expect: "Infinity"
}, {
set: {re: -Infinity, im: 0},
expect: "-Infinity"
expect: "Infinity"
}, {
set: {re: 0, im: Infinity},
expect: "Infinity"
}, {
set: {re: 0, im: -Infinity},
expect: "Infinity"
}, {
set: Complex.I,
fn: "mul",
Expand Down Expand Up @@ -83,7 +110,7 @@ var tests = [{
set: Infinity,
fn: "mul",
param: "i",
expect: "NaN"
expect: "Infinity"
}, {
set: "-36i",
fn: "sqrt",
Expand All @@ -92,7 +119,27 @@ var tests = [{
set: "4 + 2i",
fn: "div",
param: "0",
expect: "Infinity + Infinityi"
expect: "Infinity"
}, {
set: "0",
fn: "div",
param: Infinity,
expect: "0"
}, {
set: -Infinity,
fn: "div",
param: 0,
expect: "Infinity"
}, {
set: Infinity,
fn: "div",
param: Infinity,
expect: "NaN"
}, {
set: 0,
fn: "div",
param: 0,
expect: "NaN"
}, {
set: "4 + 2i",
fn: "div",
Expand All @@ -118,11 +165,26 @@ var tests = [{
fn: "mul",
param: "i",
expect: "6 + 3i"
}, {
set: Infinity,
fn: "mul",
param: 0,
expect: "NaN"
}, {
set: "3 + 4i",
fn: "add",
param: "5 - 7i",
expect: "8 - 3i"
}, {
set: Infinity,
fn: "add",
param: Infinity,
expect: "NaN"
}, {
set: -Infinity,
fn: "sub",
param: -Infinity,
expect: "NaN"
}, {
set: "6i",
fn: "div",
Expand Down Expand Up @@ -155,7 +217,7 @@ var tests = [{
}, {
set: 0,
fn: "log",
expect: "-Infinity"
expect: "Infinity"
}, {
set: Infinity,
fn: "mul",
Expand Down Expand Up @@ -321,7 +383,7 @@ var tests = [{
set: -Infinity,
fn: "div",
param: 3,
expect: "-Infinity"
expect: "Infinity"
}, {
set: {re: 1, im: 2},
fn: "add",
Expand Down Expand Up @@ -385,7 +447,7 @@ var tests = [{
set: "0-0i",
fn: "pow",
param: 0,
expect: "0"
expect: "1"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind "0^0"=1?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as x -> 0, x^x -> 1 could be the reason

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that makes sense. But typically 0^0 is undefined. So 0 wasn't the best option either, but the question is if 0^0 should be defined as 1. I mean, Math.pow(0,0)=1 having it for complex numbers equal to one would make it consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a very good point. It seems like it should be either Complex.ONE or Complex.NAN. The IEEE floating point standard defines it both ways according to wikipedia: https://en.wikipedia.org/wiki/Zero_to_the_power_of_zero#IEEE_floating-point_standard

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say let's go with Complex.ONE with a small note in the readme.

}, {
set: "1 + 4i",
fn: "sqrt",
Expand Down Expand Up @@ -470,6 +532,10 @@ var tests = [{
}, {
set: "0",
fn: "inverse",
expect: "Infinity"
}, {
set: Infinity,
fn: "inverse",
expect: "0"
}, {
set: Complex['EPSILON'],
Expand Down Expand Up @@ -644,7 +710,7 @@ describe("Complex Details", function () {
assert.equal(one.mul(one).toString(), Complex(0, 2).toString());
assert.equal(one.div(2).toString(), "0.5 + 0.5i");
assert.equal(one.div(one).toString(), "1");
assert.equal(one.div(0).toString(), "Infinity + Infinityi");
assert.equal(one.div(0).toString(), "Infinity");
assert.equal(one.exp().toString(), "1.4686939399158851 + 2.2873552871788423i");
assert.equal(one.log().toString(), "0.34657359027997264 + 0.7853981633974483i");
assert.equal(one.pow(one).toString(), "0.2739572538301211 + 0.5837007587586147i");
Expand Down