Skip to content

Instantly share code, notes, and snippets.

@pyrocto
Last active January 25, 2024 19:13
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pyrocto/5a068100abd5ff6dfbe69a73bbc510d7 to your computer and use it in GitHub Desktop.
Save pyrocto/5a068100abd5ff6dfbe69a73bbc510d7 to your computer and use it in GitHub Desktop.
A modest proposal for operator overloading in JavaScript
/*
It is a melancholy object-oriented language to those who walk through
this great town or travel in the country, when they see the JavaScript
programs crowded with method calls begging for more succinct syntax.
I think it is agreed by all parties, that whoever could find out a
fair, cheap and easy method of making these operators sound and useful
members of the language, would deserve so well of the publick, as
to have his statue set up for a preserver of the nation. I do therefore
humbly offer to publick consideration a pure JS library I've written
for overloading operators, which permits computations like these:
Complex numbers:
>> Complex()({r: 2, i: 0} / {r: 1, i: 1} + {r: -3, i: 2}))
<- {r: -2, i: 1}
Automatic differentiation:
Let f(x) = x^3 - 5x:
>> var f = x => Dual()(x * x * x - {x:5, dx:0} * x);
Now map it over some values:
>> [-2,-1,0,1,2].map(a=>({x:a,dx:1})).map(f).map(a=>a.dx)
<- [ 7, -2, -5, -2, 7 ]
i.e. f'(x) = 3x^2 - 5.
Polynoomials:
>> Poly()([1,-2,3,-4]*[5,-6]).map((c,p)=>''+c+'x^'+p).join(' + ')
<- "5x^0 + -16x^1 + 27x^2 + -38x^3 + 24x^4"
Big rational numbers (with concise syntax!):
// 112341324242 / 22341234124 + 334123124 / 5242342
// > with(Rat){Rat()(n112341324242d22341234124 + n334123124d5242342).join(' / ')}
// "2013413645483934535 / 29280097495019602"
The implementation is only mildly worse than eating babies.
*/
((global) => {
// Increase for more complicated fancier expressions
var numVars = 5;
var vars = Array.from(Array(numVars)).map((_,i)=>i);
var randoms = vars.map(() => Math.random());
var table = {};
// n is number of internal nodes
// f is a function to process each result
var trees = (n, f) => {
  // h is the "height", thinking of 1 as a step up and 0 as a step down
  // s is the current state
  var enumerate = (n, h, s, f) => {
    if (n === 0 && h === 0) {
      f(s + '0');
    } else {
      if (h > 0) {
        enumerate(n, h - 1, s + '0', f)
      }
      if (n > 0) {
        enumerate(n - 1, h + 1, s + '1', f)
      }
    }
  };
  enumerate(n, 0, '', f);
};
var toFunction = (s, pos, opCount, varCount) => {
if (s[pos] == '0') {
return [`x[${varCount}]`, pos + 1, opCount, varCount + 1];
}
var left, right, op;
pos++;
op = `ops[${opCount}]`;
[left, pos, opCount, varCount] = toFunction(s, pos, opCount + 1, varCount);
[right, pos, opCount, varCount] = toFunction(s, pos, opCount, varCount);
return [`${op}(${left},${right})`, pos, opCount, varCount];
};
var add = (x,y) => x+y; add.toString = ()=>'+';
var sub = (x,y) => x-y; sub.toString = ()=>'-';
var mul = (x,y) => x*y; mul.toString = ()=>'*';
var div = (x,y) => x/y; div.toString = ()=>'/';
var round = (x) => {
var million = Math.pow(2,20);
return Math.floor(x * million) / million;
};
var populate = (expr, ops, opCount) => {
if (ops.length == opCount) {
var result;
var order=[];
var x = vars.map(y => ({
valueOf:()=>{
order.push(y);
return randoms[order.length - 1];
}
}));
with ({ops, x}) { result = round(eval(expr)); }
table[result] = {ops: ops.map(x => '' + x), expr, order};
} else {
populate(expr, ops.concat(add), opCount);
populate(expr, ops.concat(sub), opCount);
populate(expr, ops.concat(mul), opCount);
populate(expr, ops.concat(div), opCount);
}
};
var allExprs = (s) => {
var [expr, , opCount, ] = toFunction(s, 0, 0, 0);
populate(expr, [], opCount);
};
vars.forEach(x=>trees(x, allExprs));
var makeContext = (constr, opTable) => () => {
// Set up values array
var V = [];
// Install temporary valueOf
var valueOf = constr.prototype.valueOf;
constr.prototype.valueOf = function () {
return randoms[V.push(this) - 1];
};
// Return function expecting key
return (key) => {
var {ops, expr, order} = table[round(+key)];
constr.prototype.valueOf = valueOf;
var result;
var index = 0;
var W = [];
V.forEach((v, i) => W[order[i]] = V[i]);
with ({ops: ops.map(x => opTable[x]), x: W}) { result = eval(expr); }
V = [];
return result;
};
};
global.makeContext = makeContext;
})(this);
var Complex = makeContext(Object, {
'+': (x, y) => ({r: x.r + y.r, i: x.i + y.i}),
'-': (x, y) => ({r: x.r - y.r, i: x.i - y.i}),
'*': (x, y) => ({r: x.r * y.r - x.i * y.i, i: x.r * y.i + x.i * y.r}),
'/': (x, y) => {
const norm = y.r**2 + y.i**2;
return {r: (x.r * y.r + x.i * y.i) / norm, i: (x.i * y.r - x.r * y.i) / norm};
}
});
var Dual = makeContext(Object, {
'+': (a, b) => ({x: a.x + b.x, dx: a.dx + b.dx}),
'-': (a, b) => ({x: a.x - b.x, dx: a.dx - b.dx}),
'*': (a, b) => ({x: a.x * b.x, dx: a.x * b.dx + a.dx * b.x}),
'/': (a, b) => ({x: a.x / b.x, dx: (a.dx * b.x - a.x * b.dx) / (b.x ** 2)})
});
var Poly = makeContext(Array, {
'+': (a, b) => (a.length >= b.length ? a.map((x,i) => x + (b[i]?b[i]:0)) : b.map((x,i) => x + (a[i]?a[i]:0))),
'-': (a, b) => (a.length >= b.length ? a.map((x,i) => x - (b[i]?b[i]:0)) : b.map((x,i) => x - (a[i]?a[i]:0))),
'*': (a, b) => {
var result = [];
for (var i = 0; i < a.length; ++i) {
for (var j = 0; j < b.length; ++j) {
result[i+j] = result[i+j] ? result[i+j] : 0;
result[i+j] += a[i] * b[j];
}
}
return result;
},
'/': (a, b) => { throw new Error('Not implemented'); }
});
var Str = new Proxy(makeContext(Array, {
'+':(a,b) => [a[0]+b[0]],
'*':(a,b) => [Array.from(Array(b[0])).map(x=>a[0]).join('')]
}), {
has (target, key) { return key.match(/^[a-z0-9A-Z_]+$/) && key !== 'Str'; },
get(target, prop, receiver) {
if (typeof prop == 'string') {
return [prop];
} else {
return target[prop];
}
}
});
function reduce(numerator,denominator) {
var gcd = function gcd(a,b){
return b ? gcd(b, a%b) : a;
};
gcd = gcd(numerator,denominator);
return [numerator/gcd, denominator/gcd];
}
var numDenom = /^n([0-9]+)d([0-9]+)$/;
var Rat = new Proxy(makeContext(Array, {
'+':(a,b) => reduce(a[0]*b[1] + a[1]*b[0], a[1]*b[1]),
'-':(a,b) => reduce(a[0]*b[1] - a[1]*b[0], a[1]*b[1]),
'*':(a,b) => reduce(a[0]*b[0], a[1]*b[1]),
'/':(a,b) => reduce(a[0]*b[1], a[1]*b[0])
}), {
has (target, key) { return !!key.match(numDenom); },
get(target, prop, receiver) {
if (typeof prop == 'string') {
var m = prop.match(numDenom);
return reduce(BigInt(m[1]), BigInt(m[2]));
} else {
return target[prop];
}
}
});
@fuunnx
Copy link

fuunnx commented Aug 27, 2020

Sir, I don't get how it works, I need explanations 🤯

@pyrocto
Copy link
Author

pyrocto commented Aug 27, 2020

There are four evil kludges at play here. The first is one that various others have discovered: the arithmetic operators do coersion on their operands, and if the operand is an object (as opposed to a primitive), then its valueOf method gets invoked. So the question becomes, "What value should valueOf return?" The result of coersion has to be a number or a string, and multiplication only works with numbers, so we have to return a number.

People have tried packing data about the operand into JavaScript's IEEE 754 floats, but they only hold 64 bits. So if you have a Point(x, y) class, then you can represent it as the number x * 10**12 + y or some such, but the points have much more limited precision and you can only add and subtract them. This is almost useless for polynomials.

The second trick is to have the valueOf method store this in a table (line 123) and use the result of the expression to figure out what to do with the values in the table. Others have also discovered this before. This way we get to store all the information about the objects we're manipulating, but it's still not clear what number to return. This guy uses the number 3 to be able to do a single subtraction, a single division, multiple additions, or multiple multiplications.

The third trick allows us to use arbitrary expressions. It depends on an observation from abstract algebra: numbers chosen uniformly at random from the unit interval are transcendental with probability 1. That implies there are no two fundamentally different arithmetic expressions using each random number once and +-*/ that have the same value. With IEEE 754 floats, I can uniquely distinguish about 2**64 different expressions by their result. So I run through all possible arithmetic expressions in numVars variables (lines 49-66 enumerate the structure of the AST while 68-78 turn it into JS), evaluate the expression with the variables set to the random numbers, and store the expression in a table under the result (90-113).

There's a complication in that JavaScript has operator precedence: in the expression 3-4*5, the multiplication is done first and then the subtraction. So in evaluating the expression, I have to keep track of the order in which objects get coerced (96-97). When I get the coerced result of an expression, I look up the original expression in the table (126-127), permute the objects based on the order they get coerced (132), and then execute the expression with the appropriate methods corresponding to +-*/ (133).

The last trick gets used in Str and Rat above. In a with block of the form with(obj){ body }, if a variable name mentioned in body is a property name of obj, then the property is returned. I use a Proxy for obj (174, 198) with handlers that claim every possible property of a certain form (the has method on lines 178 and 204). Str claims to have all properties that match the regex /^[a-z0-9A-Z_]+$/ other than Str, while Rat claims to have all properties that match the regex /^n([0-9]+)d([0-9]+)$/ (a numerator followed by a denominator). When asked what value those variables have, the Proxy responds with the appropriate data structure: Str says the value of prop is [prop] (181), while Rat says the value of n<numerator>d<denominator> is the pair [<numerator>, <denominator>], where I use BigInt to make sure there's no loss in precision and reduce so the fraction is always in lowest form (208).

@pyrocto
Copy link
Author

pyrocto commented Aug 27, 2020

By the way, the title and the comment text at the top come from A Modest Proposal by Johnathan Swift. It's a satirical piece drawing attention to the plight of the poor Irish in which he proposes that Britain adopt cannibalism and that the poor should sell their babies to butchers in order to have enough money to feed their families and afford a place to live.

@fuunnx
Copy link

fuunnx commented Aug 28, 2020

It's clearer, that's clever ! Now I have the taste of babies in mouth, thank you 😄

Didn't know of this piece, I like it !

@pyrocto
Copy link
Author

pyrocto commented Aug 28, 2020

Haha thx!

@nullhook
Copy link

nullhook commented Dec 24, 2020

This looks clever. Can valueOf differ per operator and can it handle constants?

@pyrocto
Copy link
Author

pyrocto commented Dec 24, 2020

This looks clever.

Thanks!

Can valueOf differ per operator

No, it's the same for all objects (see line 122). The only difference is how the context (Complex, Dual, Poly, etc.) processes the result from valueOf().

and can it handle constants?

It can, but you have to think of them as needing an explicit cast. For example, you have to represent the complex number 2 as {r:2, i:0}, so you might want to write a function like let cast=x=>({r:x, i:0}) to do the cast from reals to the type the context expects.

@dy
Copy link

dy commented Aug 24, 2021

That proposal has a lot of potential.
From what I can see:

  • use BigInt to allow any-precision ops
  • pipe operator a | b | c
  • units with (units) { 10em + 10px }

@pyrocto
Copy link
Author

pyrocto commented Aug 24, 2021

It would be straightforward to wrap BigInts to do fixed-point arithmetic, but for irrational numbers, you'd probably want a stream-based implementation. This gist could help you keep the client code cleaner.

I suppose it might be possible to overload pipe if you generate random numbers in the range [0, 2^32) rather than in [0, 1). If you used the latter, the random numbers would all be coerced to 0.

For computing with units, the symbols have to be valid identifiers, so you'd either have to put the unit before the quantity:
with (units) { em10 + px10 }
or prefix the quantity with some symbol like $ or _ or something :
with (units) { $10em + $10px }

But all this is a big joke; the real tc39 operator overloading proposal can be found here.

@jasoncortese
Copy link

Awesome, I had done something similar years ago, and was just thinking that modern js may make some of that easier. I will include a link to it if I can find it -- hopefully will provide some inspiration as yours has for me.

One of the uses I found was for something I was calling ValentNumbers, where a number can have multiple values. Useful to define a SQRT2, which alternates between [Math.sqrt(2), 2/Math.sqrt(2)] so that: SQRT2 * SQRT2 == 2 (instead of 2.000000004). Or INV7, which alternates between [1/7, 1/7, (1-6/7)].

A few thoughts:

  • "with" won't work in strict mode
  • create a Context class which returns a Proxy, then add the "+"/symbol methods to the handlers object passed in to the proxy
  • love the dual numbers example!

@jasoncortese
Copy link

jasoncortese commented Dec 7, 2023

I make no claims as to the readiness of this code -- this seems to be a more recent attempt than I recall...

/*
 * mathlib.js
 *
 */

class Primitive extends Number {
    static list = [];
    static stack = [];

    constructor(n) {
        super(n ?? Math.random());
        const parent = this.constructor;
        parent.list.push(this);
    }

    valueOf() {
        const parent = this.constructor;
        parent.stack.push(this);
        setTimeout(() => parent.stack.pop(), 1);
        return this;
    }
}

class Tuple extends Primitive {
    constructor(...args) {
        super();
        var buffer = new ArrayBuffer(4 * args.length);
        var matrix = new Float32Array(buffer);
        matrix.__defineGetter__('length', () => args.length);
        matrix.__defineSetter__('length', () => args.length);
    }

    parse(r, n, m, op) {
        var primitives = Tuple.primitives = Tuple.primitives || [];
        return (primitives.find(n => r == n)) ||
            ((n = primitives.find(n => m = primitives.find(m => op = findop(r, n, m)))) && n[op](m)) ||
            ((n = primitives.find(n => op = findopMatrix(r, n))) && n[op](m)) ||
            ((n = primitives.find(n => op = findopInt(r, n))) && n[op](m)) ||
            ((n = primitives.find(n => op = findopFloat(r, n))) && n[op](m)) ||
            ((n = r.constructor()) && (n.primitive = r) || n);

        function findop(r, n, m) {
            if (r == n * m) return 'times';
            if (r == n / m) return 'over';
            if (r == m / n) return 'into';
            if (r == n + m) return 'plus';
            if (r == n - m) return 'minus';
            if (r == m - n) return 'from';
        }

        function findopMatrix(r, n) {
            if (r == 0 - n) return 'neg';
            if (r == 1 / n) return 'inv';
            //if (r == n.adjugate)    return 'adj';
            //if (r == n.transpose)   return 'trans';
            //if (r == n.conjugate)   return 'conj';
            //if (r == n.transjugate) return 'transj';
        }

        function findopInt(r, n) {
            if (r / n == (r / n) >> 0) return m = r / n, 'times';
            if (n / r == (n / r) >> 0) return m = n / r, 'over';
            if (n * r == (n * r) >> 0) return m = n * r, 'into';
            if (r - n == (r - n) >> 0) return m = r - n, 'plus';
            if (n - r == (n - r) >> 0) return m = n - r, 'minus';
            if (n + r == (n + r) >> 0) return m = n + r, 'from';
        }

        function findopFloat(r, n) {
            if (r.insignificand(7) == (r - n).insignificand(7)) return m = (r - n).significand(7), 'plus';
            if (r.insignificand(7) == (n - r).insignificand(7)) return m = (n - r).significand(7), 'minus';
            if (r.insignificand(7) == (n + r).insignificand(7)) return m = (n + r).significand(7), 'from';
        }
    }
}

class Scalar extends Tuple {
    constructor(...args) {
        super(...args.slice(0, 1));
        this.order = 1;
        Object.defineProperties(this, {
            transpose: {get: () => Scalar(this[0])},
        });
    }
}

class Vector extends Tuple {
    constructor(...args) {
        super(...args.slice(0, 2));
        this.order = 2;
        Object.defineProperties(this, {
            transpose: {get: (b) => (b = Matrix(2,1)) && b.set(a) || b},
        });
    }
}

class Matrix2 extends Tuple {
    constructor(...args) {
        super(...args.slice(0, 4));
        this.order = 2;
        //this.primitive = arguments[4] || a.primitive;
    }

    tr    = () => this[0] + this[3]; //trace
    det   = () => Math.abs(this[0] * this[3] - this[1] * this[2]); //determinant
    adj   = () => Matrix2(this[3], -this[1], -this[2], this[0]); //adjugate
    inv   = () => Tuple.parse(this.adj / this.det); //-1 = inverse
    trans = () => Matrix2(this[0], this[2], this[1], this[3]); //T = transpose
    conj  = () => Matrix2(this[0], -this[1], -this[2], this[3]); //conjugate
    tranj = () => Matrix2(this[0], -this[2], -this[1], this[3]); //H = transjugate
    neg   = () => Matrix2(-this[0], -this[1], -this[2], -this[3]); //(-) = negative
    exp   = () => this.det - this.tr * this.tr / 4; //? = exponent

    times = (b) => Matrix2(Matrix2['*'][b.constructor.name](this, b));
    over  = (b) => Matrix2(Matrix2['*'][b.constructor.name](this, 1 / b));
    into  = (b) => Matrix2(Matrix2['*'][b.constructor.name](this.inv, b));
    dot   = (b) => Matrix2(Matrix2['*'][b.constructor.name](this.trans, b));
    plus  = (b) => Matrix2(Matrix2['+'][b.constructor.name](this, b));
    minus = (b) => Matrix2(Matrix2['+'][b.constructor.name](this, -b));
    from  = (b) => Matrix2(Matrix2['+'][b.constructor.name](this.neg, b));

    ['*'] = {
        Number: (a, b) => [
            a[0] * b,
            a[1] * b,
            a[2] * b,
            a[3] * b
        ],
        Scalar: (a, b) => [
            a[0] * b[0],
            a[1] * b[0],
            a[2] * b[0],
            a[3] * b[0]
        ],
        Vector: (a, b) => [
            a[0] * b[0],
            a[1] * b[0],
            a[2] * b[1],
            a[3] * b[1]
        ],
        Matrix2: (a, b) => [
            a[0] * b[0] + a[2] * b[1],
            a[1] * b[0] + a[3] * b[1],
            a[2] * b[3] + a[0] * b[2],
            a[3] * b[3] + a[1] * b[2]
        ],
    };

    ['+'] = {
        Number: (a, b) => [
            a[0] + b,
            a[1],
            a[2],
            a[3] + b
        ],
        Scalar: (a, b) => [
            a[0] + b[0],
            a[1],
            a[2],
            a[3] + b[0]
        ],
        Vector: (a, b) => [
            a[0] + b[0],
            a[1] + b[1],
            a[2] + b[1],
            a[3] + b[0]
        ],
        Matrix2: (a, b) => [
            a[0] + b[0],
            a[1] + b[1],
            a[2] + b[2],
            a[3] + b[3]
        ],
    };

    _exp() {
        a.delta = () => sqrt((a[0] - a[3]) * (a[0] - a[3]) + (4 * a[1] * a[2]));
        a.exp = () => Matrix2(a.exp_0, a.exp_1, a.exp_2, a.exp_3) / a.delta;
        a.exp[1] = () => Math.exp((a[0] + a[3]) / 2) * (2 * a[1]) * Math.sinh(a.delta / 2);
        a.exp[2] = () => Math.exp((a[0] + a[3]) / 2) * (2 * a[2]) * Math.sinh(a.delta / 2);
        a.exp[3] = () => Math.exp((a[0] + a[3]) / 2) * (a.delta * Math.cosh(a.delta / 2) - (a[0] - a[3]) * Math.sinh(a.delta / 2));
        a.exp[0] = () => Math.exp((a[0] + a[3]) / 2) * (a.delta * Math.cosh(a.delta / 2) + (a[0] - a[3]) * Math.sinh(a.delta / 2));
        a.exp_est = () => Math.exp((a[0] + a[3]) / 2) * Matrix2((1 + (a[0] - a[3]) / 2), a[1], a[2], (1 - (a[0] - a[3]) / 2));
    }
}

const l = new Lookup();


class Matrix3 extends Tuple {
    constructor() {
        super(...args.slice(0, 9));
        a.trace = () => (a[0] + a[3]);
        a.determinant = () => Math.abs(0
            + a[0] * a[4] * a[8] + a[1] * a[5] * a[6] + a[2] * a[3] * a[7]
            - a[2] * a[4] * a[6] - a[1] * a[3] * a[8] - a[0] * a[5] * a[7]);
        a.transpose = () => Matrix2(a[0], a[1], a[2], a[3]); //TODO
        a.adjoint = () => Matrix2(a[3], -a[2], -a[1], a[0]); //TODO
        a.negative = () => Matrix2(-a[0], -a[2], -a[1], -a[3]); //TODO
        a.inverse = () => Tuple.parse(a.determinant() * a.adjoint());
        //constant.call(window);
    }
}

class MatrixSquare extends Tuple {
    constructor() {
        super(...args.slice(0));
        var a = new Tuple([].concat.apply([], arguments));
        a.constructor = Matrix2;
        a.trace = () => [].reduce.call(a, (t,e,i) => t += i % (a.order + 1) ? 0 : e);
        a.minor = (y,z) => ((z = y / a.order | 0), (y = y % a.order), [].filter.call(a, (x,i,j) => ((j = i / a.order | 0), (i = i % a.order), i != y && j != z)));
        a.cofactor = (y,z) => ((z = y / a.order | 0), (y = y % a.order), [].filter.call(a, (x,i,j) => ((j = i / a.order | 0), (i = i % a.order), i != y && j != z)));
        a.determinant = () => [].reduce.call(a, (t,e,i) => i > a.order ? t : a.minor(i).times(i % 2 ? -e : e));
        //constant.call(window);
    }
}

function MatrixDiagonal() {
    a.exp = () => MatrixDiag(Math.pow(ê, a[0]), Math.pow(ê, a[3]));
}

function NaturalNumber(n) {
    var buffer = new ArrayBuffer(4);
    var natural = new Int32Array(buffer);
    natural.constructor = NaturalNumber;
    natural.set(n);
    return natural;
}

function IntegralNumber(n) {
    var buffer = new ArrayBuffer(4);
    var integer = new Uint32Array(buffer);
    integer.constructor = IntegralNumber;
    integer.set(n);
    return integer;
}

function RationalNumber(n, d) {
    var rational = ε.times(Number(n));
    infinitesimal.constructor = InfinitesimalNumber;
    return infinitesimal;
}

function ValentNumber() {
    var a = new Tuple([].concat.apply([], arguments));
    a.valueOf = () => ([].unshift.call(a, [].slice.call(a, -1)), a[0]);
    a.constructor = ValentNumber;
    return a;
}

function DuplexNumber(n, m) {
    var infinitesimal = ε.times(Number(n));
    infinitesimal.constructor = InfinitesimalNumber;
    return infinitesimal;
}

function ContinuedFractionNumber() {
    var rational = ε.times(Number(n));
    infinitesimal.constructor = InfinitesimalNumber;
    return infinitesimal;
}

function RealNumber(n) {
    var buffer = new ArrayBuffer(4);
    var real = new Float32Array(buffer);
    real.constructor = RealNumber;
    real.set(n);
    return real;
}

function ComplexNumber(n, m) {
    //nn = n; nm = mn = m; mm = -n;
    var imaginary = î.times(Number(n));
    imaginary.constructor = ImaginaryNumber;
    return imaginary;
}

function QuaternionNumber(n, m1, m2, m3) {
    var e = new Matrix2(1, 0, 0, 1);
    var i = new Matrix2(0, 1,-1, 0);
    var j = new Matrix2(0, 1,-1, 0);
    var k = new Matrix2(0, 1,-1, 0);
    var quaternion = new Tuple(n * e, m1 * i, m2 * j, m3 * k);
    //nn = n; nm = mn = m; mm = -n; m1m2 = -m2m1 = m3; m1m2m3 = -n;
    quaternion.constructor = QuaternionNumber;
    quaternion.conj = () => e - i - j - k;
    quaternion.scalar = (a) => (a + a.conj) / 2;
    quaternion.vector = (a) => (a - a.conj) / 2;
    quaternion.norm = (a) => sqrt(a * a.conj);
    quaternion.det = (a) => sqrt(a * a.conj);
    quaternion.plus = (a, b) => [
        a[0] + b[0],
        a[1] + b[1],
        a[2] + b[2],
        a[3] + b[3]
    ];
    quaternion.times = (a, b) => [
        a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3],
        a[0] * b[1] + a[1] * b[0] + a[2] * b[3] - a[3] * b[2],
        a[0] * b[2] - a[1] * b[3] + a[2] * b[0] - a[3] * b[1],
        a[0] * b[3] + a[1] * b[2] - a[2] * b[1] + a[3] * b[0]
    ];
    quaternion.vector.dot = (a, b) => a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
    quaternion.vector.cross = (a, b) => (1) + (2) + (3);
    return quaternion;
}

function OctonionNumber(n, m1, m2, m3, m4, m5, m6, m7) {
    //nn = n; nm = mn = m; mm = -n; m3m4 = -m4m3 = m7
    //conjugate = n - m1 - m2 - m3 - m4 - m5 - m6 - m7;
    var octonion = î.times(Number(m1));
    octonion.constructor = OctonionNumber;
    return octonion;
}

function SedenionNumber(n, m1, m2, m3, m4, m5, m6, m7, m8, m9, mA, mB, mC, mD, mE, mF) {
    //nn = n; nm = mn = m; mm = -n; m7m8 = -m8m7 = mF; m1(m2m3) = -(m1m2)m3;
    var sedenion = î.times(Number(n));
    sedenion.constructor = SedenionNumber;
    return sedenion;
}

function PerplexNumber(n, m) {
    var unitesimal = ĵ.times(Number(n));
    unitesimal.constructor = UnitesimalNumber;
    return unitesimal;
}

function CoQuaternionNumber(n, m1, m2, m3) {
    //conjugate = w − xi − yj − zk
    var coquaternion = î.times(Number(n));
    coquaternion.constructor = CoQuaternionNumber;
    return coquaternion;
}



const Ω = 1/0;
const Ʊ = 0/0;
const î = new Matrix2(0, 1,-1, 0, 0.20787957635076190854695561983497877003387784163176960807); //imaginary, sqrt(-1)
const ĵ = new Matrix2(0, 1, 1, 0, 0.99999999999999994448884876874217297881841659545898437499); //unitesimal, sqrt(1)
const ε = new Matrix2(0, 1, 0, 0, 1.57172778470262867296192256111765622564011176170918094545e-162); //infinitesimal, sqrt(0)
const ω = new Matrix2(0, 1, Ω, 0, 1.79769313486231580793728971405303415079934132710037826936e+308); //infinite, sqrt(1/0)
const I = new Matrix2(1, 0, 0, 1, 1.0); //IdentityMatrix
const Ø = new Matrix2(0, 0, 0, 0, 0.0); //ZeroMatrix
const δ = 0.00000000000000011102230246251565404236316680908203125001; //Number.EPSILON/2 ~ 2^-53
const π = 3.14159265358979323846264338327950288419716939937510582097;
const ℮ = 2.71828182845904523536028747135266249775724709369995957496;
const φ = 1.61803398874989484820458683436563811772030917980576286213;
const γ = 0.57721566490153286060651209008240243104215933593992359880;

const ℕ = NaturalNumber;
const ℤ = IntegralNumber;
const ℚ = RationalNumber;
const ℝ = RealNumber;
const ℂ = ComplexNumber;
const ℍ = QuaternionNumber;
const ℙ = PerplexNumber;


const SQRT2 = new ValentNumber(
    1.41421356237309504880168872420969807856967187537694807317,
    1.41421356237309481240771447119186632335186004638671875000);

const SQRT3 = new ValentNumber(
    1.73205080756887729352744634150587236694280525381038062805,
    1.73205080756887724868775535469467286020517349243164062501);

const SQRT5 = new ValentNumber(
    2.23606797749978969640917366873127623544061835961152572427,
    2.23606797749978956912908500953562906943261623382568359375);

const SQRT7 = new ValentNumber(
    2.64575131106459059050161575363926042571025918308245018036,
    2.64575131106459043861534041752747725695371627807617187501);

const SQRT11 = new ValentNumber(
    3.31662479035539984911493273667068668392708854558935359705,
    3.31662479035539984911493273667068668392708854558935359704);

const SQRT13 = new ValentNumber(
    3.60555127546398929311922126747049594625129657384524621271,
    3.60555127546398929311922126747049594625129657384524621270);

const SQRT17 = new ValentNumber(
    4.12310562561766054982140985597407702514719922537362043439,
    4.12310562561766054982140985597407702514719922537362043438);

const SQRT19 = new ValentNumber(
    4.35889894354067355223698198385961565913700392523244493689,
    4.35889894354067355223698198385961565913700392523244493688);

const RCPR3 = new ValentNumber(
    0.33333333333333333333333333333333333333333333333333333333,
    0.33333333333333333333333333333333333333333333333333333333,
    0.33333333333333333333333333333333333333333333333333333334);

const RCPR7 = new ValentNumber(
    0.14285714285714285714285714285714285714285714285714285715,
    0.14285714285714285814285714285714285714285714285714285714,
    0.14285714285714285914285714285714285714285714285714285715);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment