| 1 | //! Natural log (ln) approximation for a single-precision float. |
| 2 | //! |
| 3 | //! Method described at: <https://stackoverflow.com/a/44232045/> |
| 4 | //! |
| 5 | //! Modified to not be restricted to int range and only values of x above 1.0. |
| 6 | //! Also got rid of most of the slow conversions. Should work for all positive values of x. |
| 7 | |
| 8 | use super::{EXPONENT_MASK, F32}; |
| 9 | use core::f32::consts::LN_2; |
| 10 | |
| 11 | impl F32 { |
| 12 | /// Approximates the natural logarithm of the number. |
| 13 | // Note: excessive precision ignored because it hides the origin of the numbers used for the |
| 14 | // ln(1.0->2.0) polynomial |
| 15 | #[allow (clippy::excessive_precision)] |
| 16 | pub fn ln(self) -> Self { |
| 17 | // x may essentially be 1.0 but, as clippy notes, these kinds of |
| 18 | // floating point comparisons can fail when the bit pattern is not the sames |
| 19 | if (self - Self::ONE).abs() < f32::EPSILON { |
| 20 | return Self::ZERO; |
| 21 | } |
| 22 | |
| 23 | let x_less_than_1 = self < 1.0; |
| 24 | |
| 25 | // Note: we could use the fast inverse approximation here found in super::inv::inv_approx, but |
| 26 | // the precision of such an approximation is assumed not good enough. |
| 27 | let x_working = if x_less_than_1 { self.inv() } else { self }; |
| 28 | |
| 29 | // according to the SO post ln(x) = ln((2^n)*y)= ln(2^n) + ln(y) = ln(2) * n + ln(y) |
| 30 | // get exponent value |
| 31 | let base2_exponent = x_working.extract_exponent_value() as u32; |
| 32 | let divisor = f32::from_bits(x_working.to_bits() & EXPONENT_MASK); |
| 33 | |
| 34 | // supposedly normalizing between 1.0 and 2.0 |
| 35 | let x_working = x_working / divisor; |
| 36 | |
| 37 | // approximate polynomial generated from maple in the post using Remez Algorithm: |
| 38 | // https://en.wikipedia.org/wiki/Remez_algorithm |
| 39 | let ln_1to2_polynomial = -1.741_793_9 |
| 40 | + (2.821_202_6 |
| 41 | + (-1.469_956_8 + (0.447_179_55 - 0.056_570_851 * x_working) * x_working) |
| 42 | * x_working) |
| 43 | * x_working; |
| 44 | |
| 45 | // ln(2) * n + ln(y) |
| 46 | let result = (base2_exponent as f32) * LN_2 + ln_1to2_polynomial; |
| 47 | |
| 48 | if x_less_than_1 { |
| 49 | -result |
| 50 | } else { |
| 51 | result |
| 52 | } |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | #[cfg (test)] |
| 57 | mod tests { |
| 58 | use super::F32; |
| 59 | |
| 60 | pub(crate) const MAX_ERROR: f32 = 0.001; |
| 61 | |
| 62 | /// ln(x) test vectors - `(input, output)` |
| 63 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ |
| 64 | (1e-20, -46.0517), |
| 65 | (1e-19, -43.749115), |
| 66 | (1e-18, -41.446533), |
| 67 | (1e-17, -39.143948), |
| 68 | (1e-16, -36.841362), |
| 69 | (1e-15, -34.538776), |
| 70 | (1e-14, -32.23619), |
| 71 | (1e-13, -29.933607), |
| 72 | (1e-12, -27.631021), |
| 73 | (1e-11, -25.328436), |
| 74 | (1e-10, -23.02585), |
| 75 | (1e-09, -20.723267), |
| 76 | (1e-08, -18.420681), |
| 77 | (1e-07, -16.118095), |
| 78 | (1e-06, -13.815511), |
| 79 | (1e-05, -11.512925), |
| 80 | (1e-04, -9.2103405), |
| 81 | (0.001, -6.9077554), |
| 82 | (0.01, -4.6051702), |
| 83 | (0.1, -2.3025851), |
| 84 | (10.0, 2.3025851), |
| 85 | (100.0, 4.6051702), |
| 86 | (1000.0, 6.9077554), |
| 87 | (10000.0, 9.2103405), |
| 88 | (100000.0, 11.512925), |
| 89 | (1000000.0, 13.815511), |
| 90 | (10000000.0, 16.118095), |
| 91 | (100000000.0, 18.420681), |
| 92 | (1000000000.0, 20.723267), |
| 93 | (10000000000.0, 23.02585), |
| 94 | (100000000000.0, 25.328436), |
| 95 | (1000000000000.0, 27.631021), |
| 96 | (10000000000000.0, 29.933607), |
| 97 | (100000000000000.0, 32.23619), |
| 98 | (1000000000000000.0, 34.538776), |
| 99 | (1e+16, 36.841362), |
| 100 | (1e+17, 39.143948), |
| 101 | (1e+18, 41.446533), |
| 102 | (1e+19, 43.749115), |
| 103 | ]; |
| 104 | |
| 105 | #[test ] |
| 106 | fn sanity_check() { |
| 107 | assert_eq!(F32::ONE.ln(), F32::ZERO); |
| 108 | for &(x, expected) in TEST_VECTORS { |
| 109 | let ln_x = F32(x).ln(); |
| 110 | let relative_error = (ln_x - expected).abs() / expected; |
| 111 | |
| 112 | assert!( |
| 113 | relative_error <= MAX_ERROR, |
| 114 | "relative_error {} too large: {} vs {}" , |
| 115 | relative_error, |
| 116 | ln_x, |
| 117 | expected |
| 118 | ); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |