double.js
Version:
Emulated float128 or double-double arithmetic. A floating point expansion with 31 accurate decimal digits.
292 lines (278 loc) • 11.6 kB
JavaScript
import { test } from 'https://cdn.jsdelivr.net/npm/zora@3.0.3/dist/bundle/module.js';
import Double from '../dist/double.esm.js';
let D = Double;
let eps1 = 1e-15;
let eps2 = 1e-30;
let abs = Math.abs;
let diff, diff2, expected, expected2, actual, actual2, actual3;
test('IEEE verification in your browser (based on ref. [4])', t => {
expected = Infinity;
actual = (1e308 + 1e308) / 1e308;
t.ok(actual == expected, 'Infinity');
actual = 0/0;
t.ok(isNaN(actual), 'NaN');
expected = 1.0000000000000002;
actual = 1 / (1 - Math.pow(2,-53))
t.ok(actual == expected, 'double rounding 1');
expected = Infinity;
actual = 1e1023 * (2 - Math.pow(2, -52))
t.ok(actual == expected, 'double rounding 2');
expected = 9007199254740994;
actual = 1 - 1/65536 + 9007199254740994;
t.ok(actual == expected, 'SSE2 rounding 1');
expected = 1.1125369292536007e-308;
actual = 1.5 * 2.2250738585072014e-308 - 2.2250738585072014e-308;
t.ok(actual == expected, 'SSE2 rounding 2');
});
test('constructor test', t => {
let d = new D(0.3);
t.ok(d instanceof D, 'constructed from number');
let d2 = new D('0.3');
t.ok(d2 instanceof D, 'constructed from string');
let d3 = new D({ hi: 0.3, lo: 0 });
t.ok(d3 instanceof D, 'constructed from object');
let d4 = new D([0.3, 0]);
t.ok(d4 instanceof D, 'constructed from array');
let d5 = new D(d);
t.deepEqual(d3, d, 'fromObject() equal to fromNumber()');
t.deepEqual(d4, d, 'fromArray() equal to fromNumber()');
t.deepEqual(d5, d, 'clone() equal to fromString()');
t.ok(d.toNumber() == 0.3, 'toNumber() equal to number');
});
test('classic test', t => {
expected = new D('0.2');
actual = new D('0.3').sub(new D('0.1'));
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, '0.3-0.1 = 0.2 (result = ' + actual.toNumber() +', diff=' + diff + ')');
});
test('unary operators with double', t => {
expected = D.One;
actual = D.Log2.mul(D.Log2.inv());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'inv2(x) * x (diff=' + diff + ')');
expected = D.Zero;
actual = D.Log2.add(D.Log2.neg());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'neg2(x) + x (diff=' + diff + ')');
expected = D.Pi;
actual = D.Pi.neg().abs();
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'abs2(x) (diff=' + diff + ')');
expected = D.Log2;
actual = D.Log2.sqr().sqrt();
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'sqr2 (sqrt2 (x)) (diff=' + diff + ')');
expected = D.Log2;
actual = D.Log2.ln().exp();
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2,'exp2( ln2 (x)) (diff=' + diff + ')');
expected = D.One;
actual = D.Log2.cosh().sqr().sub(D.Log2.sinh().sqr());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2,'cosh(x)² - sinh(x)² = 1 (diff=' + diff + ')');
});
test('double-single operations', t => {
expected = D.Pi;
actual = D.Pi.add(D.E.hi).add(-D.E.hi);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'add21 with inverted (diff=' + diff + ')');
expected = D.Pi;
actual = D.Pi.sub(D.E.hi).sub(-D.E.hi);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'sub21 with inverted (diff=' + diff + ')');
expected = D.Pi;
actual = D.mul22(D.mul21(D.Pi, D.E.hi), new D(D.E.hi).inv());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'mul21 with inverted (diff=' + diff + ')');
expected = D.Pi;
actual = D.div22(D.div21(D.Pi, D.E.hi), new D(D.E.hi).inv());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'div21 with inverted (diff=' + diff + ')');
expected = D.E;
actual = D.E.mul(D.Pi.hi).div(D.Pi.hi);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'mul21/div21 inverse (diff=' + diff + ')');
expected = 1e20; expected2 = 1e-20;
actual = new D([10, 0]).pown(20).toNumber(); actual2 = new D([10, 0]).pown(-20).toNumber();
diff = abs(actual - expected); diff2 = abs(actual2 - expected2);
t.ok(diff < eps2 && diff2 < eps2,'pow2n (diff=' + diff + ', diff2=' + diff2 + ')');
});
test('double-double operations', t => {
expected = D.Log2;
actual = D.Log2.add(D.E).sub(D.E);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'additive inverse (diff=' + diff + ')');
expected = D.Pi;
actual = D.Pi.mul(D.Log2).div(D.Log2);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'multiplicative inverse (diff=' + diff + ')');
expected = D.Pi;
actual = D.Pi.pow(D.E).pow(D.E.inv());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'pow22 (diff=' + diff + ')');
});
test('fromSum11 / fromMul11 / fromSqr1', t => {
expected = new D(1024);
actual = D.fromSum11(512, 512);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'fromSum11 (diff=' + diff + ')');
expected = new D(1024);
actual = D.fromMul11(32, 32);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'fromMul11 (diff=' + diff + ')');
expected = new D(1024);
actual = D.fromSqr1(32);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'fromSqr1 (diff=' + diff + ')');
});
test('fromString', t => {
expected = 123456789;
actual = new D('123456789Q').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'integer numbers (diff=' + diff + ')');
expected = -100;
actual = new D(-100).toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'negative numbers (diff=' + diff + ')');
expected = 654321.789;
actual = new D(' 654321.789').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'fractional (diff=' + diff + ')');
expected = 120;
actual = new D('12e1').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'exponent format (diff=' + diff + ')');
expected = 1.2;
actual = new D('12e-1').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps1, 'negative exponent (diff=' + diff + ')');
expected = -0.123;
actual = new D('-.123R').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'short defenition (diff=' + diff + ')');
expected = 123.12e6;
actual = new D('123.12e6').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'scientific format (diff=' + diff + ')');
expected = 123e12;
actual = new D('123e12.6').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'mixed up (diff=' + diff + ')');
expected = 456.12;
actual = new D('456.12.6').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'two dot (diff=' + diff + ')');
expected = 123e-12;
actual = new D('123e-12e6').toNumber();
diff = abs(expected - actual);
t.ok(diff < eps2, 'two exp (diff=' + diff + ')');
expected = 9e300;
actual = new D('9e300');
diff = abs(expected - actual.toNumber());
t.ok(diff < Infinity, 'large exponent (diff=' + diff + ')');
expected = 0;
actual = new D('9e-322');
diff = abs(expected - actual.toNumber());
t.ok(diff < eps2, 'Tiny exponent (diff=' + diff + ')');
actual = new D('1e500').toNumber();
actual2 = new D('-1e500').toNumber();
t.ok(actual === Infinity && actual2 === -Infinity, 'Giant exponent');
actual = new D('1e-500').toNumber();
actual2 = new D('-1e-500').toNumber();
t.ok(actual === 0 && actual2 === 0, 'Insignificant exponent');
actual = D.Zero.toNumber();
t.ok(actual === 0, 'Zero number');
actual = new D(Infinity).toNumber();
actual2 = new D('-Infinity').toNumber();
actual3 = new D('Infinity').toNumber();
t.ok(actual === Infinity && actual2 === -Infinity && actual3 === Infinity, 'Infinity number');
actual = D.NaN.toNumber();
actual2 = new D('SDLFK').toNumber();
actual3 = new D(' ').toNumber();
t.ok(isNaN(actual) && isNaN(actual2) && isNaN(actual3), 'NaN number');
});
test('toExponential', t => {
expected = new D('3.1415926535897932384626433832795');
actual = new D(new D('3.1415926535897932384626433832795').toExponential());
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'double -> string -> double (diff=' + diff + ')');
expected = 'NaN';
actual = D.NaN.toExponential();
t.ok(actual == expected, 'NaN');
expected = 'Infinity';
actual = D.Infinity.toExponential();
t.ok(actual == expected, 'Infinity');
expected = '0e+0'
actual = new D(0).toExponential();
t.ok(actual == expected, 'Zero');
expected = '-';
actual = new D(-0.5).toExponential().slice(0, 1);
t.ok(actual == expected, 'Negative');
});
test('constants', t => {
expected = D.Pi;
actual = new D('3.1415926535897932384626433832795');
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'pi (diff=' + diff + ')');
expected = D.X2Pi;
actual = new D('6.2831853071795864769252867665590');
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'x2pi (diff=' + diff + ')');
expected = D.E;
actual = new D('2.7182818284590452353602874713526');
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'e (diff=' + diff + ')');
expected = D.Log2;
actual = new D('0.69314718055994530941723212145817');
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'ln(2) (diff=' + diff + ')');
expected = D.Phi;
actual = new D('1.6180339887498948482045868343656');
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'phi (diff=' + diff + ')');
expected = new D('3.1415926535897932384626433832795');
actual = new D('4.7123889803846898576939650749192').mul(new D('0.66666666666666666666666666666666'));
diff = expected.sub(actual).abs().toNumber();
})
test('comparisons', t => {
t.ok(D.Pi.eq(D.Pi.mul(D.One)) && D.Pi.ne(D.Log2) && D.Zero.eq(0) && D.One.ne(2), 'eq, ne (true)');
t.ok(!D.Pi.eq(D.Log2) && !D.Pi.ne(D.Pi) && !D.Zero.eq(D.Log2) && !D.One.ne(1), 'eq, ne (false)');
t.ok(D.Pi.lt(D.X2Pi) && D.Pi.le(D.X2Pi) && D.Pi.lt(4) && D.Pi.le(4), 'lt, le (true)');
t.ok(!D.Pi.lt(D.Pi) && !D.Pi.le(D.One) && !D.One.lt(1) && !D.Pi.le(1), 'lt, le (false)');
t.ok(D.X2Pi.gt(D.Pi) && D.X2Pi.ge(D.Pi) && D.Pi.gt(2) && D.Pi.ge(2), 'gt, ge (true)');
t.ok(!D.One.gt(D.One) && !D.Pi.ge(4) && !D.Zero.gt(0) && !D.Pi.ge(4), 'gt, ge (false)');
});
test('exp2 / ln2', t => {
expected = Double.One;
actual = Double.Zero.exp();
t.ok(expected.eq(actual), 'exp(0) = 1');
expected = Double.E;
actual = Double.One.exp();
t.ok(expected.eq(actual), 'exp(1) = e');
expected = Double.Zero;
actual = Double.One.ln();
t.ok(expected.eq(actual), 'ln(1) = 0');
expected = Double.MinusInfinity;
actual = Double.Zero.ln();
t.ok(expected.eq(actual), 'ln(1) = 0');
});
test('extended tests', t => {
let phi = new D([5, 0]).sqrt().add(1).div(2);
expected = new D(phi).add(1);
actual = new D(phi).sqr();
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'ϕ² = ϕ + 1 (diff=' + diff + ')');
expected = new D(phi).inv();
actual = phi.sub(1);
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, '1/ϕ = ϕ - 1 (diff=' + diff + ')');
t.ok(diff < eps2, 'parsed mult (3*pi/2) * (2/3) (diff=' + diff + ')');
expected = new D('5.8598744820488384738229308546321');
actual = new D('3.1415926535897932384626433832795').add(new D('2.7182818284590452353602874713526'));
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'parsed add (e + pi) (diff=' + diff + ')');
expected = new D('1.7724538509055160272981674833411');
actual = new D('3.1415926535897932384626433832795').sqrt();
diff = expected.sub(actual).abs().toNumber();
t.ok(diff < eps2, 'parsed sqt (sqrt(pi)) (diff=' + diff + ')');
})