generators.js (5818B)
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 abs = require( './../../../../base/special/abs' ); 24 var FLOAT32_SMALLEST_NORMAL = require( '@stdlib/constants/float32/smallest-normal' ); 25 var EPS = require( '@stdlib/constants/float64/eps' ); 26 27 28 // VARIABLES // 29 30 var MAX_ITER = 1000000; 31 32 33 // FUNCTIONS // 34 35 /** 36 * Evaluates a continued fraction expansion. 37 * 38 * ```text 39 * a1 40 * --------------- 41 * b1 + a2 42 * ---------- 43 * b2 + a3 44 * ----- 45 * b3 + ... 46 * ``` 47 * 48 * @private 49 * @param {Function} gen - function giving terms of continued fraction expansion 50 * @param {PositiveNumber} factor - further terms are only added as long as factor*result is smaller than the next term 51 * @param {PositiveInteger} maxIter - maximum number of iterations 52 * @returns {number} evaluated expansion 53 */ 54 function continuedFractionA( gen, factor, maxIter ) { 55 var isgenerator; 56 var delta; 57 var a0; 58 var f; 59 var C; 60 var D; 61 var v; 62 63 isgenerator = typeof gen.next === 'function'; 64 v = ( isgenerator ) ? gen.next().value : gen(); 65 f = v[ 1 ]; 66 a0 = v[ 0 ]; 67 if ( f === 0.0 ) { 68 f = FLOAT32_SMALLEST_NORMAL; 69 } 70 C = f; 71 D = 0; 72 if ( isgenerator === true ) { 73 do { 74 v = gen.next().value; 75 if ( v ) { 76 D = v[ 1 ] + ( v[ 0 ] * D ); 77 if ( D === 0.0 ) { 78 D = FLOAT32_SMALLEST_NORMAL; 79 } 80 C = v[ 1 ] + ( v[ 0 ] / C ); 81 if ( C === 0.0 ) { 82 C = FLOAT32_SMALLEST_NORMAL; 83 } 84 D = 1.0 / D; 85 delta = C * D; 86 f *= delta; 87 } 88 } while ( ( abs( delta - 1.0 ) > factor ) && --maxIter ); // eslint-disable-line no-plusplus 89 } else { 90 do { 91 v = gen(); 92 if ( v ) { 93 D = v[ 1 ] + ( v[ 0 ] * D ); 94 if ( D === 0.0 ) { 95 D = FLOAT32_SMALLEST_NORMAL; 96 } 97 C = v[ 1 ] + ( v[ 0 ] / C ); 98 if ( C === 0.0 ) { 99 C = FLOAT32_SMALLEST_NORMAL; 100 } 101 D = 1.0 / D; 102 delta = C * D; 103 f *= delta; 104 } 105 } while ( v && ( abs( delta - 1.0 ) > factor ) && --maxIter ); // eslint-disable-line no-plusplus 106 } 107 return a0 / f; 108 } 109 110 /** 111 * Evaluates a continued fraction expansion. 112 * 113 * ```text 114 * b0 + a1 115 * --------------- 116 * b1 + a2 117 * ---------- 118 * b2 + a3 119 * ----- 120 * b3 + ... 121 * ``` 122 * 123 * @private 124 * @param {Function} gen - function giving terms of continued fraction expansion 125 * @param {PositiveNumber} factor - further terms are only added as long as factor*result is smaller than the next term 126 * @param {PositiveInteger} maxIter - maximum number of iterations 127 * @returns {number} evaluated expansion 128 */ 129 function continuedFractionB( gen, factor, maxIter ) { 130 var isgenerator; 131 var delta; 132 var f; 133 var C; 134 var D; 135 var v; 136 137 isgenerator = typeof gen.next === 'function'; 138 v = ( isgenerator ) ? gen.next().value : gen(); 139 f = v[ 1 ]; 140 if ( f === 0.0 ) { 141 f = FLOAT32_SMALLEST_NORMAL; 142 } 143 C = f; 144 D = 0.0; 145 if ( isgenerator === true ) { 146 do { 147 v = gen.next().value; 148 if ( v ) { 149 D = v[ 1 ] + ( v[ 0 ] * D ); 150 if ( D === 0.0 ) { 151 D = FLOAT32_SMALLEST_NORMAL; 152 } 153 C = v[ 1 ] + ( v[ 0 ] / C ); 154 if ( C === 0.0 ) { 155 C = FLOAT32_SMALLEST_NORMAL; 156 } 157 D = 1.0 / D; 158 delta = C * D; 159 f *= delta; 160 } 161 } while ( v && ( abs( delta - 1.0 ) > factor ) && --maxIter ); // eslint-disable-line no-plusplus 162 } else { 163 do { 164 v = gen(); 165 if ( v ) { 166 D = v[ 1 ] + ( v[ 0 ] * D ); 167 if ( D === 0.0 ) { 168 D = FLOAT32_SMALLEST_NORMAL; 169 } 170 C = v[ 1 ] + ( v[ 0 ] / C ); 171 if ( C === 0.0 ) { 172 C = FLOAT32_SMALLEST_NORMAL; 173 } 174 D = 1.0 / D; 175 delta = C * D; 176 f *= delta; 177 } 178 } while ( v && ( abs( delta - 1.0 ) > factor ) && --maxIter ); // eslint-disable-line no-plusplus 179 } 180 return f; 181 } 182 183 184 // MAIN // 185 186 /** 187 * Evaluates the continued fraction approximation for the supplied series generator using the modified Lentz algorithm. 188 * 189 * ## References 190 * 191 * - Lentz, William J. 1976. "Generating bessel functions in Mie scattering calculations using continued fractions." _Applied Optics_ 15 (3): 668–71. doi:[10.1364/AO.15.000668](https://doi.org/10.1364/AO.15.000668). 192 * 193 * @param {Function} generator - function returning terms of continued fraction expansion 194 * @param {Object} [options] - function options 195 * @param {PositiveInteger} [options.maxIter=1000] - maximum number of iterations 196 * @param {PositiveNumber} [options.tolerance=2.22e-16] - further terms are only added as long as the next term is greater than current term times the tolerance 197 * @param {boolean} [options.keep=false] - whether to keep the leading b term 198 * @returns {number} value of continued fraction 199 * 200 * @example 201 * // Continued fraction for (e-1)^(-1): 202 * var gen = generator(); 203 * var out = continuedFraction( gen ); 204 * // returns ~0.582 205 * 206 * function* generator() { 207 * var i = 0; 208 * while ( true ) { 209 * i++; 210 * yield [ i, i ]; 211 * } 212 * } 213 */ 214 function continuedFraction( generator, options ) { 215 var maxIter; 216 var opts; 217 var eps; 218 219 opts = {}; 220 if ( arguments.length > 1 ) { 221 opts = options; 222 } 223 maxIter = opts.maxIter || MAX_ITER; 224 eps = opts.tolerance || EPS; 225 226 if ( opts.keep ) { 227 return continuedFractionB( generator, eps, maxIter ); 228 } 229 return continuedFractionA( generator, eps, maxIter ); 230 } 231 232 233 // EXPORTS // 234 235 module.exports = continuedFraction;