Math in V8 is Broken

Athan Reines / @kgryte

Standard Library

ES5

  • acos
  • asin
  • atan
  • atan2
  • cos
  • sin
  • tan
  • abs
  • exp
  • log (ln)
  • pow
  • sqrt
  • ceil
  • floor
  • round
  • max
  • min
  • random

ES2015/ES6

  • acosh
  • asinh
  • atanh
  • cosh
  • sinh
  • tanh
  • sign
  • cbrt
  • expm1
  • log10
  • log1p
  • log2
  • fround
  • trunc
  • hypot
  • clz32
  • imul

Comparison

Golang

  • Abs
  • Acosh
  • Asin
  • Asinh
  • Atan
  • Atan2
  • Atanh
  • Cbrt
  • Ceil
  • Copysign
  • Cos
  • Cosh
  • Dim
  • Erf
  • Erfc
  • Exp
  • Exp2
  • Expm1
  • Float32bits
  • Float64bits
  • Floor
  • Frexp
  • Gamma
  • Hypot
  • Ilogb
  • J0
  • J1
  • Jn
  • Ldexp
  • Lgamma
  • Log
  • Log10
  • Log1p
  • Log2
  • Logb
  • Max
  • Min
  • Mod
  • Modf
  • Nextafter

Golang (cont.)

  • NextAfter32
  • Pow
  • Pow10
  • Signbit
  • Sin
  • Sincos
  • Sinh
  • Sqrt
  • Tan
  • Tanh
  • Trunc
  • Y0
  • Y1
  • Yn
  • ExpFloat64
  • Float64
  • Int
  • Int63
  • NormFloat64
  • Perm
  • Uint32
  • Zipf
  • (Complex)
  • (Big)

So what?

Bugs

V8 and Node

Node EOL V8 Release
0.10.44 10-2016 3.14.5.9 05-2013
0.12.17 01-2017 3.28.71.19 11-2014
4.6.2 04-2018 4.5.103.42 08-2015
5.12.0 06-2016 4.6.85.32 11-2015
6.9.1 04-2019 5.1.281.84 07-2016
7.2.0 06-2017 5.4.500.43 11-2016

Math.sin/Math.cos


var x = Math.pow( 2, 120 );
// returns 1.329227995784916e+36

var y = Math.sin( x );
// returns: -0.8783788442551665
// expected: 0.377820109360752

y = Math.cos( x );
// returns:   0.47796506772457525
// expected: -0.9258790228548378
				
  • Node v0.10

NaN


var nan1 = 0.0 / 0.0;
var nan2 = Number.POSITIVE_INFINITY / Number.POSITIVE_INFINITY;

var arr = [ nan1, nan2 ];
var FLOAT64_VIEW = new Float64Array( arr );
var INT32_VIEW = new Int32Array( FLOAT64_VIEW.buffer );

var isnan1 = ( FLOAT64_VIEW[0] !== FLOAT64_VIEW[0] );
// returns true

var isnan2 = ( FLOAT64_VIEW[1] !== FLOAT64_VIEW[1] );
// returns true

var bool = ( nan1 !== nan2 );
// returns true

var int11 = INT32_VIEW[ 0 ];
var int12 = INT32_VIEW[ 1 ];
var int21 = INT32_VIEW[ 2 ];
var int22 = INT32_VIEW[ 3 ];

console.log( '%d|%d and %d|%d', int11, int12, int21, int22 );
// => 0|2146959360 and 0|-524288
				
  • Node v0.12
  • Node v4
  • Node v6
  • Node v7
  • Node v0.10*

Math.pow


var x = Math.pow( 10, 308 );
// returns:  1.0000000000000006e+308
// expected: 1.0e+308
				
  • Node v0.10+

Algorithm


function pow( x, y ) {
    var m = x;
    var n = y;
    var p = 1;
    while ( n !== 0 ) {
        if ( ( n & 1 ) !== 0 ) {
            p *= m;
        }
        m *= m;
        if ( ( n & 2 ) !== 0 ) {
            p *= m;
        }
        m *= m;
        n >>= 2;
    }
    return p;
}
				

Math.atanh


var y = Math.atanh( 1.0e-10 );
// returns:  1.000000082640371e-10
// expected: 1.0e-10
				
  • Node v0.12
  • Node v4
  • Node v6

Math.acosh


var y = Math.acosh( 1.0 + 1.0e-10 );
// returns:  0.000014142136208733941
// expected: 0.000014142136208675862
				
  • Node v0.12
  • Node v4
  • Node v6

Math.asinh


var y = Math.asinh( 1.0e-50 );
// returns:  0.0
// expected: 1.0e-50

y = Math.asinh( 1.0e200 );
// returns   +infinity
// expected: 461.2101657793691
				
  • Node v0.12
  • Node v4
  • Node v6

Algorithm


function asinh( x ) {
    if ( x === 0 || !isFinite( x ) ) {
        return x;
    }
    if ( x > 0 ) {
        return Math.log( x + Math.sqrt( x*x + 1 ) );
    }
    return -Math.log( -x + Math.sqrt( x*x + 1 ) );
}
				

Algorithm


function asinh( x ) {
    var sgn;
    var xx;
    var t;
    if ( isnan( x ) || isinfinite( x ) ) {
        return x;
    }
    if ( x < 0.0 ) {
        x = -x;
        sgn = true;
    }
    // Case: |x| < 2**-28
    if ( x < NEAR_ZERO ) {
        t = x;
    }
    // Case: |x| > 2**28
    else if ( x > HUGE ) {
        t = ln( x ) + LN2;
    }
    // Case: 2**28 > |x| > 2.0
    else if ( x > 2.0 ) {
        t = ln( 2.0*x + 1.0/(sqrt(x*x + 1.0) + x) );
    }
    // Case: 2.0 > |x| > 2**-28
    else {
        xx = x * x;
        t = log1p( x + xx/(1.0 + sqrt(1.0 + xx)) );
    }
    return sgn ? -t : t;
}
				

Math.exp


var y = Math.exp( 100.0 );
// returns:  2.6881171418161485e+43
// expected: 2.6881171418161356e+43
				
  • Node v0.12
  • Node v4
  • Node v6

Math.exp

Math.random


uint32_t state0 = 1;
uint32_t state1 = 2;
uint32_t mwc1616() {
  state0 = 18030 * (state0 & 0xffff) + (state0 >> 16);
  state1 = 30903 * (state1 & 0xffff) + (state1 >> 16);
  return state0 << 16 + (state1 & 0xffff);
}
				
  • Node v0.10
  • Node v0.12
  • Node v4

Example


var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';

function randomString( length ) {
    var randint;
    var str;
    var i;

    str = '';
    for ( i = 0; i < length; i++ ) {
        randint = Math.floor( Math.random() * ALPHABET.length );
        str += ALPHABET.substring( randint, randint+1 );
    }
    return str;
}
					

 

Betable

Math.random

 

V8 blog

JavaScript

  • Underspecification
  • Cross-browser variability
  • No single codebase
  • Versioning
  • Required shims
  • Globals
  • Testing
  • No golden algorithms
  • Timescale
  • Trust

Community

Polyfills


var isFinite = require( 'is-finite' );

module.exports = function asinh( x ) {
    if ( x === 0 || !isFinite( x ) ) {
        return x;
    }
    return x < 0 ? -asinh( -x ) : Math.log( x + Math.sqrt( x*x + 1 ) );
}
				

Libraries


function asinh( x ) {
  return Math.log( Math.sqrt( x*x + 1 ) + x );
}
				

:(

stdlib

github.com/stdlib-js/stdlib

Opportunities

Get Involved!

Advocate

  • Int64/Uint64
  • Int128/Uint128
  • Typed Objects (complex)
  • SIMD (long)
  • Parallelism
  • GPGPU
  • BigFloat/BigInt

Thank you!

Athan Reines / @kgryte

https://github.com/stdlib-js/stdlib

Appendix






















				

What can be done at the specification level?

Integer Support

Typed Objects

WebCL

SIMD

Parallel Computing

Operator Overloading

The End