time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

main.js (5759B)


      1 /**
      2 * @license Apache-2.0
      3 *
      4 * Copyright (c) 2018 The Stdlib Authors.
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *    http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 'use strict';
     20 
     21 // MODULES //
     22 
     23 var isPositiveInteger = require( '@stdlib/assert/is-positive-integer' ).isPrimitive;
     24 var isnan = require( '@stdlib/math/base/assert/is-nan' );
     25 var isInfinite = require( '@stdlib/math/base/assert/is-infinite' );
     26 var frexp = require( '@stdlib/math/base/special/frexp' );
     27 var ldexp = require( '@stdlib/math/base/special/ldexp' );
     28 var Float64Array = require( '@stdlib/array/float64' );
     29 
     30 
     31 // FUNCTIONS //
     32 
     33 /**
     34 * Computes an updated product.
     35 *
     36 * @private
     37 * @param {Array} workspace - workspace array
     38 * @param {Object} acc - accumulated fractional and exponent parts
     39 * @param {number} x - multiplicative factor
     40 * @returns {number} product
     41 */
     42 function product( workspace, acc, x ) {
     43 	// Split the incoming value into a normalized fraction and exponent:
     44 	frexp( workspace, x );
     45 
     46 	// Update the accumulated fraction:
     47 	acc.frac *= workspace[ 0 ];
     48 
     49 	// Update the accumulated exponent:
     50 	acc.exp += workspace[ 1 ];
     51 
     52 	// Ensure fraction remains normalized to avoid overflow/underflow...
     53 	if ( acc.frac > -0.5 && acc.frac < 0.5 ) {
     54 		frexp( workspace, acc.frac );
     55 		acc.frac = workspace[ 0 ];
     56 		acc.exp += workspace[ 1 ];
     57 	}
     58 	return ldexp( acc.frac, acc.exp );
     59 }
     60 
     61 
     62 // MAIN //
     63 
     64 /**
     65 * Returns an accumulator function which incrementally computes a moving product.
     66 *
     67 * ## Method
     68 *
     69 * To avoid overflow/underflow, we store the fractional and exponent parts of intermediate results separately. By keeping a normalized fraction, we prevent underflow/overflow of the fraction. Underflow of the exponent is impossible, as IEEE 754 floating-point exponents are integer values. Overflow of the exponent is possible, but highly unlikely. In the worst case, an intermediate exponent is greater than the minimum safe integer, and adding the exponent of an incoming value does not change the intermediate result. While incorrect, such behavior does not lead to exponent overflow.
     70 *
     71 * While intermediate results are largely immune to overflow and not subject to underflow, this does not mean that returned results will never be zero or infinite. In fact, zero (underflow) and infinite (overflow) results may be transient (i.e., infinity followed by a finite number).
     72 *
     73 *
     74 * ## References
     75 *
     76 * -   Ueberhuber, Christoph W. 1997. _Numerical Computation 1: Methods, Software, and Analysis_. Springer-Verlag Berlin Heidelberg. doi:[10.1007/978-3-642-59118-1](https://doi.org/10.1007/978-3-642-59118-1).
     77 *
     78 * @param {PositiveInteger} W - window size
     79 * @throws {TypeError} must provide a positive integer
     80 * @returns {Function} accumulator function
     81 *
     82 * @example
     83 * var accumulator = incrmprod( 3 );
     84 *
     85 * var p = accumulator();
     86 * // returns null
     87 *
     88 * p = accumulator( 2.0 );
     89 * // returns 2.0
     90 *
     91 * p = accumulator( -5.0 );
     92 * // returns -10.0
     93 *
     94 * p = accumulator( 3.0 );
     95 * // returns -30.0
     96 *
     97 * p = accumulator( 5.0 );
     98 * // returns -75.0
     99 *
    100 * p = accumulator();
    101 * // returns -75.0
    102 */
    103 function incrmprod( W ) {
    104 	var parts;
    105 	var prod;
    106 	var buf;
    107 	var acc;
    108 	var N;
    109 	var i;
    110 	if ( !isPositiveInteger( W ) ) {
    111 		throw new TypeError( 'invalid argument. Must provide a positive integer. Value: `' + W + '`.' );
    112 	}
    113 	buf = new Float64Array( W );
    114 	i = -1;
    115 	N = 0;
    116 
    117 	// Initialize a workspace for `frexp`:
    118 	parts = [ 0.0, 0 ];
    119 
    120 	// Initial product is 1.0, which may be split into its fractional and exponent parts (0.5 x 2.0**1 = 1.0):
    121 	prod = 1.0;
    122 	acc = {};
    123 	acc.frac = 0.5;
    124 	acc.exp = 1.0;
    125 
    126 	return accumulator;
    127 
    128 	/**
    129 	* If provided a value, the accumulator function returns an updated prodct. If not provided a value, the accumulator function returns the current prodct.
    130 	*
    131 	* @private
    132 	* @param {number} [x] - input value
    133 	* @returns {(number|null)} product or null
    134 	*/
    135 	function accumulator( x ) {
    136 		var k;
    137 		var v;
    138 		if ( arguments.length === 0 ) {
    139 			if ( N === 0 ) {
    140 				return null;
    141 			}
    142 			return prod;
    143 		}
    144 		// Update the index for managing the circular buffer:
    145 		i = (i+1) % W;
    146 
    147 		// Case: incoming value is NaN, the accumulated value is automatically NaN...
    148 		if ( isnan( x ) ) {
    149 			N = W; // explicitly set to avoid `N < W` branch
    150 			prod = NaN;
    151 		}
    152 		// Case: initial window...
    153 		else if ( N < W ) {
    154 			N += 1;
    155 			prod = product( parts, acc, x );
    156 		}
    157 		// Case: outgoing value is a "special" value, and, thus, we need to compute the accumulated value...
    158 		else if (
    159 			buf[ i ] === 0.0 ||
    160 			isnan( buf[ i ] ) ||
    161 			isInfinite( buf[ i ] )
    162 		) {
    163 			N = 1;
    164 			acc.frac = 0.5;
    165 			acc.exp = 1.0;
    166 			product( parts, acc, x );
    167 			for ( k = 0; k < W; k++ ) {
    168 				if ( k !== i ) {
    169 					v = buf[ k ];
    170 					if ( isnan( v ) ) {
    171 						N = W; // explicitly set to avoid `N < W` branch
    172 						prod = NaN;
    173 						break; // product is automatically NaN, so no need to continue
    174 					}
    175 					N += 1;
    176 					prod = product( parts, acc, v );
    177 				}
    178 			}
    179 		}
    180 		// Case: neither the current accumulated value nor the incoming value are NaN, so we need to update the accumulated value...
    181 		else if ( isnan( prod ) === false ) {
    182 			v = x / buf[ i ];
    183 			prod = product( parts, acc, v );
    184 		}
    185 		// Case: the current accumulated value is NaN, so nothing to do until the buffer no longer contains NaN values...
    186 		buf[ i ] = x;
    187 
    188 		return prod;
    189 	}
    190 }
    191 
    192 
    193 // EXPORTS //
    194 
    195 module.exports = incrmprod;