roundn.js (5992B)
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 isnan = require( './../../../../base/assert/is-nan' ); 24 var isInfinite = require( './../../../../base/assert/is-infinite' ); 25 var pow = require( './../../../../base/special/pow' ); 26 var abs = require( './../../../../base/special/abs' ); 27 var round = require( './../../../../base/special/round' ); 28 var MAX_SAFE_INTEGER = require( '@stdlib/constants/float64/max-safe-integer' ); 29 var MAX_EXP = require( '@stdlib/constants/float64/max-base10-exponent' ); 30 var MIN_EXP = require( '@stdlib/constants/float64/min-base10-exponent' ); 31 var MIN_EXP_SUBNORMAL = require( '@stdlib/constants/float64/min-base10-exponent-subnormal' ); 32 33 34 // VARIABLES // 35 36 var MAX_INT = MAX_SAFE_INTEGER + 1; 37 var HUGE = 1.0e+308; 38 39 40 // MAIN // 41 42 /** 43 * Rounds a numeric value to the nearest multiple of \\(10^n\\). 44 * 45 * ## Method 46 * 47 * 1. If \\(|x| <= 2^{53}\\) and \\(|n| <= 308\\), we can use the formula 48 * 49 * ```tex 50 * \operatorname{roundn}(x,n) = \frac{\operatorname{round}(x \cdot 10^{-n})}{10^{-n}} 51 * ``` 52 * 53 * which shifts the decimal to the nearest multiple of \\(10^n\\), performs a standard \\(\mathrm{round}\\) operation, and then shifts the decimal to its original position. 54 * 55 * <!-- <note> --> 56 * 57 * If \\(x \cdot 10^{-n}\\) overflows, \\(x\\) lacks a sufficient number of decimal digits to have any effect when rounding. Accordingly, the rounded value is \\(x\\). 58 * 59 * <!-- </note> --> 60 * 61 * <!-- <note> --> 62 * 63 * Note that rescaling \\(x\\) can result in unexpected behavior. For instance, the result of \\(\operatorname{roundn}(0.2+0.1,-16)\\) is \\(0.3000000000000001\\) and not \\(0.3\\). While possibly unexpected, this is not a bug. The behavior stems from the fact that most decimal fractions cannot be exactly represented as floating-point numbers. And further, rescaling can lead to slightly different fractional values, which, in turn, affects the result of \\(\mathrm{round}\\). 64 * 65 * <!-- </note> --> 66 * 67 * 2. If \\(n > 308\\), we recognize that the maximum absolute double-precision floating-point number is \\(\approx 1.8\mbox{e}308\\) and, thus, the result of rounding any possible finite number \\(x\\) to the nearest \\(10^n\\) is \\(0.0\\). To ensure consistent behavior with \\(\operatorname{round}(x)\\), the sign of \\(x\\) is preserved. 68 * 69 * 3. If \\(n < -324\\), \\(n\\) exceeds the maximum number of possible decimal places (such as with subnormal numbers), and, thus, the rounded value is \\(x\\). 70 * 71 * 4. If \\(x > 2^{53}\\), \\(x\\) is **always** an integer (i.e., \\(x\\) has no decimal digits). If \\(n <= 0\\), the rounded value is \\(x\\). 72 * 73 * 5. If \\(n < -308\\), we let \\(m = n + 308\\) and modify the above formula to avoid overflow. 74 * 75 * ```tex 76 * \operatorname{roundn}(x,n) = \frac{\biggl(\frac{\operatorname{round}( (x \cdot 10^{308}) 10^{-m})}{10^{308}}\biggr)}{10^{-m}} 77 * ``` 78 * 79 * If overflow occurs, the rounded value is \\(x\\). 80 * 81 * 82 * ## Special Cases 83 * 84 * ```tex 85 * \begin{align*} 86 * \operatorname{roundn}(\mathrm{NaN}, n) &= \mathrm{NaN} \\ 87 * \operatorname{roundn}(x, \mathrm{NaN}) &= \mathrm{NaN} \\ 88 * \operatorname{roundn}(x, \pm\infty) &= \mathrm{NaN} \\ 89 * \operatorname{roundn}(\pm\infty, n) &= \pm\infty \\ 90 * \operatorname{roundn}(\pm 0, n) &= \pm 0 91 * \end{align*} 92 * ``` 93 * 94 * ## Notes 95 * 96 * 1. Alternative algorithms: 97 * 98 * - Round by [casting][1] \\(x\\) to an exponential string. 99 * - Native Python implementation [1][2] and [2][3]. 100 * 101 * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 102 * [2]: https://hg.python.org/releasing/2.7.9/file/tip/Objects/floatobject.c#l1082 103 * [3]: https://hg.python.org/releasing/2.7.9/file/tip/Objects/floatobject.c#l1226 104 * 105 * 106 * @param {number} x - input value 107 * @param {integer} n - integer power of `10` 108 * @returns {number} rounded value 109 * 110 * @example 111 * // Round a value to 2 decimal places: 112 * var v = roundn( 3.141592653589793, -2 ); 113 * // returns 3.14 114 * 115 * @example 116 * // If n = 0, `roundn` behaves like `round`: 117 * var v = roundn( 3.141592653589793, 0 ); 118 * // returns 3.0 119 * 120 * @example 121 * // Round a value to the nearest thousand: 122 * var v = roundn( 12368.0, 3 ); 123 * // returns 12000.0 124 */ 125 function roundn( x, n ) { 126 var s; 127 var y; 128 if ( 129 isnan( x ) || 130 isnan( n ) || 131 isInfinite( n ) 132 ) { 133 return NaN; 134 } 135 if ( 136 // Handle infinities... 137 isInfinite( x ) || 138 139 // Handle +-0... 140 x === 0.0 || 141 142 // If `n` exceeds the maximum number of feasible decimal places (such as with subnormal numbers), nothing to round... 143 n < MIN_EXP_SUBNORMAL || 144 145 // If `|x|` is large enough, no decimals to round... 146 ( abs( x ) > MAX_INT && n <= 0 ) 147 ) { 148 return x; 149 } 150 // The maximum absolute double is ~1.8e308. Accordingly, any possible finite `x` rounded to the nearest >=10^309 is 0.0. 151 if ( n > MAX_EXP ) { 152 return 0.0 * x; // preserve the sign (same behavior as round) 153 } 154 // If we overflow, return `x`, as the number of digits to the right of the decimal is too small (i.e., `x` is too large / lacks sufficient fractional precision) for there to be any effect when rounding... 155 if ( n < MIN_EXP ) { 156 s = pow( 10.0, -(n + MAX_EXP) ); 157 y = (x*HUGE) * s; // order of operation matters! 158 if ( isInfinite( y ) ) { 159 return x; 160 } 161 return ( round(y)/HUGE ) / s; 162 } 163 s = pow( 10.0, -n ); 164 y = x * s; 165 if ( isInfinite( y ) ) { 166 return x; 167 } 168 return round( y ) / s; 169 } 170 171 172 // EXPORTS // 173 174 module.exports = roundn;