| 1 | // Ported from musl, which is licensed under the MIT license: |
| 2 | // https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT |
| 3 | // |
| 4 | // https://git.musl-libc.org/cgit/musl/tree/src/math/expmf.c |
| 5 | // https://git.musl-libc.org/cgit/musl/tree/src/math/expm.c |
| 6 | |
| 7 | // TODO: Updated recently. |
| 8 | |
| 9 | const std = @import("../std.zig"); |
| 10 | const math = std.math; |
| 11 | const mem = std.mem; |
| 12 | const expect = std.testing.expect; |
| 13 | const expectEqual = std.testing.expectEqual; |
| 14 | |
| 15 | /// Returns e raised to the power of x, minus 1 (e^x - 1). This is more accurate than exp(e, x) - 1 |
| 16 | /// when x is near 0. |
| 17 | /// |
| 18 | /// Special Cases: |
| 19 | /// - expm1(+inf) = +inf |
| 20 | /// - expm1(-inf) = -1 |
| 21 | /// - expm1(nan) = nan |
| 22 | pub fn expm1(x: anytype) @TypeOf(x) { |
| 23 | const T = @TypeOf(x); |
| 24 | return switch (T) { |
| 25 | f32 => expm1_32(x), |
| 26 | f64 => expm1_64(x), |
| 27 | else => @compileError("exp1m not implemented for " ++ @typeName(T)), |
| 28 | }; |
| 29 | } |
| 30 | |
| 31 | fn expm1_32(x_: f32) f32 { |
| 32 | if (math.isNan(x_)) |
| 33 | return math.nan(f32); |
| 34 | |
| 35 | const o_threshold: f32 = 8.8721679688e+01; |
| 36 | const ln2_hi: f32 = 6.9313812256e-01; |
| 37 | const ln2_lo: f32 = 9.0580006145e-06; |
| 38 | const invln2: f32 = 1.4426950216e+00; |
| 39 | const Q1: f32 = -3.3333212137e-2; |
| 40 | const Q2: f32 = 1.5807170421e-3; |
| 41 | |
| 42 | var x = x_; |
| 43 | const ux: u32 = @bitCast(x); |
| 44 | const hx = ux & 0x7FFFFFFF; |
| 45 | const sign = ux >> 31; |
| 46 | |
| 47 | // TODO: Shouldn't need this check explicitly. |
| 48 | if (math.isNegativeInf(x)) { |
| 49 | return -1.0; |
| 50 | } |
| 51 | |
| 52 | // |x| >= 27 * ln2 |
| 53 | if (hx >= 0x4195B844) { |
| 54 | // nan |
| 55 | if (hx > 0x7F800000) { |
| 56 | return x; |
| 57 | } |
| 58 | if (sign != 0) { |
| 59 | return -1; |
| 60 | } |
| 61 | if (x > o_threshold) { |
| 62 | x *= 0x1.0p127; |
| 63 | return x; |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | var hi: f32 = undefined; |
| 68 | var lo: f32 = undefined; |
| 69 | var c: f32 = undefined; |
| 70 | var k: i32 = undefined; |
| 71 | |
| 72 | // |x| > 0.5 * ln2 |
| 73 | if (hx > 0x3EB17218) { |
| 74 | // |x| < 1.5 * ln2 |
| 75 | if (hx < 0x3F851592) { |
| 76 | if (sign == 0) { |
| 77 | hi = x - ln2_hi; |
| 78 | lo = ln2_lo; |
| 79 | k = 1; |
| 80 | } else { |
| 81 | hi = x + ln2_hi; |
| 82 | lo = -ln2_lo; |
| 83 | k = -1; |
| 84 | } |
| 85 | } else { |
| 86 | var kf = invln2 * x; |
| 87 | if (sign != 0) { |
| 88 | kf -= 0.5; |
| 89 | } else { |
| 90 | kf += 0.5; |
| 91 | } |
| 92 | |
| 93 | k = @as(i32, @intFromFloat(kf)); |
| 94 | const t = @as(f32, @floatFromInt(k)); |
| 95 | hi = x - t * ln2_hi; |
| 96 | lo = t * ln2_lo; |
| 97 | } |
| 98 | |
| 99 | x = hi - lo; |
| 100 | c = (hi - x) - lo; |
| 101 | } |
| 102 | // |x| < 2^(-25) |
| 103 | else if (hx < 0x33000000) { |
| 104 | if (hx < 0x00800000) { |
| 105 | mem.doNotOptimizeAway(x * x); |
| 106 | } |
| 107 | return x; |
| 108 | } else { |
| 109 | k = 0; |
| 110 | } |
| 111 | |
| 112 | const hfx = 0.5 * x; |
| 113 | const hxs = x * hfx; |
| 114 | const r1 = 1.0 + hxs * (Q1 + hxs * Q2); |
| 115 | const t = 3.0 - r1 * hfx; |
| 116 | var e = hxs * ((r1 - t) / (6.0 - x * t)); |
| 117 | |
| 118 | // c is 0 |
| 119 | if (k == 0) { |
| 120 | return x - (x * e - hxs); |
| 121 | } |
| 122 | |
| 123 | e = x * (e - c) - c; |
| 124 | e -= hxs; |
| 125 | |
| 126 | // exp(x) ~ 2^k (x_reduced - e + 1) |
| 127 | if (k == -1) { |
| 128 | return 0.5 * (x - e) - 0.5; |
| 129 | } |
| 130 | if (k == 1) { |
| 131 | if (x < -0.25) { |
| 132 | return -2.0 * (e - (x + 0.5)); |
| 133 | } else { |
| 134 | return 1.0 + 2.0 * (x - e); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | const twopk = @as(f32, @bitCast(@as(u32, @intCast((0x7F +% k) << 23)))); |
| 139 | |
| 140 | if (k < 0 or k > 56) { |
| 141 | var y = x - e + 1.0; |
| 142 | if (k == 128) { |
| 143 | y = y * 2.0 * 0x1.0p127; |
| 144 | } else { |
| 145 | y = y * twopk; |
| 146 | } |
| 147 | |
| 148 | return y - 1.0; |
| 149 | } |
| 150 | |
| 151 | const uf: f32 = @bitCast(@as(u32, @intCast(0x7F -% k)) << 23); |
| 152 | if (k < 23) { |
| 153 | return (x - e + (1 - uf)) * twopk; |
| 154 | } else { |
| 155 | return (x - (e + uf) + 1) * twopk; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | fn expm1_64(x_: f64) f64 { |
| 160 | if (math.isNan(x_)) |
| 161 | return math.nan(f64); |
| 162 | |
| 163 | const o_threshold: f64 = 7.09782712893383973096e+02; |
| 164 | const ln2_hi: f64 = 6.93147180369123816490e-01; |
| 165 | const ln2_lo: f64 = 1.90821492927058770002e-10; |
| 166 | const invln2: f64 = 1.44269504088896338700e+00; |
| 167 | const Q1: f64 = -3.33333333333331316428e-02; |
| 168 | const Q2: f64 = 1.58730158725481460165e-03; |
| 169 | const Q3: f64 = -7.93650757867487942473e-05; |
| 170 | const Q4: f64 = 4.00821782732936239552e-06; |
| 171 | const Q5: f64 = -2.01099218183624371326e-07; |
| 172 | |
| 173 | var x = x_; |
| 174 | const ux = @as(u64, @bitCast(x)); |
| 175 | const hx = @as(u32, @intCast(ux >> 32)) & 0x7FFFFFFF; |
| 176 | const sign = ux >> 63; |
| 177 | |
| 178 | if (math.isNegativeInf(x)) { |
| 179 | return -1.0; |
| 180 | } |
| 181 | |
| 182 | // |x| >= 56 * ln2 |
| 183 | if (hx >= 0x4043687A) { |
| 184 | // exp1md(nan) = nan |
| 185 | if (hx > 0x7FF00000) { |
| 186 | return x; |
| 187 | } |
| 188 | // exp1md(-ve) = -1 |
| 189 | if (sign != 0) { |
| 190 | return -1; |
| 191 | } |
| 192 | if (x > o_threshold) { |
| 193 | math.raiseOverflow(); |
| 194 | return math.inf(f64); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | var hi: f64 = undefined; |
| 199 | var lo: f64 = undefined; |
| 200 | var c: f64 = undefined; |
| 201 | var k: i32 = undefined; |
| 202 | |
| 203 | // |x| > 0.5 * ln2 |
| 204 | if (hx > 0x3FD62E42) { |
| 205 | // |x| < 1.5 * ln2 |
| 206 | if (hx < 0x3FF0A2B2) { |
| 207 | if (sign == 0) { |
| 208 | hi = x - ln2_hi; |
| 209 | lo = ln2_lo; |
| 210 | k = 1; |
| 211 | } else { |
| 212 | hi = x + ln2_hi; |
| 213 | lo = -ln2_lo; |
| 214 | k = -1; |
| 215 | } |
| 216 | } else { |
| 217 | var kf = invln2 * x; |
| 218 | if (sign != 0) { |
| 219 | kf -= 0.5; |
| 220 | } else { |
| 221 | kf += 0.5; |
| 222 | } |
| 223 | |
| 224 | k = @as(i32, @intFromFloat(kf)); |
| 225 | const t = @as(f64, @floatFromInt(k)); |
| 226 | hi = x - t * ln2_hi; |
| 227 | lo = t * ln2_lo; |
| 228 | } |
| 229 | |
| 230 | x = hi - lo; |
| 231 | c = (hi - x) - lo; |
| 232 | } |
| 233 | // |x| < 2^(-54) |
| 234 | else if (hx < 0x3C900000) { |
| 235 | if (hx < 0x00100000) { |
| 236 | mem.doNotOptimizeAway(@as(f32, @floatCast(x))); |
| 237 | } |
| 238 | return x; |
| 239 | } else { |
| 240 | k = 0; |
| 241 | } |
| 242 | |
| 243 | const hfx = 0.5 * x; |
| 244 | const hxs = x * hfx; |
| 245 | const r1 = 1.0 + hxs * (Q1 + hxs * (Q2 + hxs * (Q3 + hxs * (Q4 + hxs * Q5)))); |
| 246 | const t = 3.0 - r1 * hfx; |
| 247 | var e = hxs * ((r1 - t) / (6.0 - x * t)); |
| 248 | |
| 249 | // c is 0 |
| 250 | if (k == 0) { |
| 251 | return x - (x * e - hxs); |
| 252 | } |
| 253 | |
| 254 | e = x * (e - c) - c; |
| 255 | e -= hxs; |
| 256 | |
| 257 | // exp(x) ~ 2^k (x_reduced - e + 1) |
| 258 | if (k == -1) { |
| 259 | return 0.5 * (x - e) - 0.5; |
| 260 | } |
| 261 | if (k == 1) { |
| 262 | if (x < -0.25) { |
| 263 | return -2.0 * (e - (x + 0.5)); |
| 264 | } else { |
| 265 | return 1.0 + 2.0 * (x - e); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | const twopk = @as(f64, @bitCast(@as(u64, @intCast(0x3FF +% k)) << 52)); |
| 270 | |
| 271 | if (k < 0 or k > 56) { |
| 272 | var y = x - e + 1.0; |
| 273 | if (k == 1024) { |
| 274 | y = y * 2.0 * 0x1.0p1023; |
| 275 | } else { |
| 276 | y = y * twopk; |
| 277 | } |
| 278 | |
| 279 | return y - 1.0; |
| 280 | } |
| 281 | |
| 282 | const uf = @as(f64, @bitCast(@as(u64, @intCast(0x3FF -% k)) << 52)); |
| 283 | if (k < 20) { |
| 284 | return (x - e + (1 - uf)) * twopk; |
| 285 | } else { |
| 286 | return (x - (e + uf) + 1) * twopk; |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | test "expm1_32() special" { |
| 291 | try expect(math.isPositiveZero(expm1_32(0.0))); |
| 292 | try expect(math.isNegativeZero(expm1_32(-0.0))); |
| 293 | try expectEqual(expm1_32(math.ln2), 1.0); |
| 294 | try expectEqual(expm1_32(math.inf(f32)), math.inf(f32)); |
| 295 | try expectEqual(expm1_32(-math.inf(f32)), -1.0); |
| 296 | try expect(math.isNan(expm1_32(math.nan(f32)))); |
| 297 | try expect(math.isNan(expm1_32(math.snan(f32)))); |
| 298 | } |
| 299 | |
| 300 | test "expm1_32() sanity" { |
| 301 | try expectEqual(expm1_32(-0x1.0223a0p+3), -0x1.ffd6e0p-1); |
| 302 | try expectEqual(expm1_32(0x1.161868p+2), 0x1.30712ap+6); |
| 303 | try expectEqual(expm1_32(-0x1.0c34b4p+3), -0x1.ffe1fap-1); |
| 304 | try expectEqual(expm1_32(-0x1.a206f0p+2), -0x1.ff4116p-1); |
| 305 | try expectEqual(expm1_32(0x1.288bbcp+3), 0x1.4ab480p+13); // Disagrees with GCC in last bit |
| 306 | try expectEqual(expm1_32(0x1.52efd0p-1), 0x1.e09536p-1); |
| 307 | try expectEqual(expm1_32(-0x1.a05cc8p-2), -0x1.561c3ep-2); |
| 308 | try expectEqual(expm1_32(0x1.1f9efap-1), 0x1.81ec4ep-1); |
| 309 | try expectEqual(expm1_32(0x1.8c5db0p-1), 0x1.2b3364p+0); |
| 310 | try expectEqual(expm1_32(-0x1.5b86eap-1), -0x1.f8951ap-2); |
| 311 | } |
| 312 | |
| 313 | test "expm1_32() boundary" { |
| 314 | // TODO: The last value before inf is actually 0x1.62e300p+6 -> 0x1.ff681ep+127 |
| 315 | // try expectEqual(expm1_32(0x1.62e42ep+6), 0x1.ffff08p+127); // Last value before result is inf |
| 316 | try expectEqual(expm1_32(0x1.62e430p+6), math.inf(f32)); // First value that gives inf |
| 317 | try expectEqual(expm1_32(0x1.fffffep+127), math.inf(f32)); // Max input value |
| 318 | try expectEqual(expm1_32(0x1p-149), 0x1p-149); // Min positive input value |
| 319 | try expectEqual(expm1_32(-0x1p-149), -0x1p-149); // Min negative input value |
| 320 | try expectEqual(expm1_32(0x1p-126), 0x1p-126); // First positive subnormal input |
| 321 | try expectEqual(expm1_32(-0x1p-126), -0x1p-126); // First negative subnormal input |
| 322 | try expectEqual(expm1_32(0x1.fffffep-125), 0x1.fffffep-125); // Last positive value before subnormal |
| 323 | try expectEqual(expm1_32(-0x1.fffffep-125), -0x1.fffffep-125); // Last negative value before subnormal |
| 324 | try expectEqual(expm1_32(-0x1.154244p+4), -0x1.fffffep-1); // Last value before result is -1 |
| 325 | try expectEqual(expm1_32(-0x1.154246p+4), -1); // First value where result is -1 |
| 326 | } |
| 327 | |
| 328 | test "expm1_64() special" { |
| 329 | try expect(math.isPositiveZero(expm1_64(0.0))); |
| 330 | try expect(math.isNegativeZero(expm1_64(-0.0))); |
| 331 | try expectEqual(expm1_64(math.ln2), 1.0); |
| 332 | try expectEqual(expm1_64(math.inf(f64)), math.inf(f64)); |
| 333 | try expectEqual(expm1_64(-math.inf(f64)), -1.0); |
| 334 | try expect(math.isNan(expm1_64(math.nan(f64)))); |
| 335 | try expect(math.isNan(expm1_64(math.snan(f64)))); |
| 336 | } |
| 337 | |
| 338 | test "expm1_64() sanity" { |
| 339 | try expectEqual(expm1_64(-0x1.02239f3c6a8f1p+3), -0x1.ffd6df9b02b3ep-1); |
| 340 | try expectEqual(expm1_64(0x1.161868e18bc67p+2), 0x1.30712ed238c04p+6); |
| 341 | try expectEqual(expm1_64(-0x1.0c34b3e01e6e7p+3), -0x1.ffe1f94e493e7p-1); |
| 342 | try expectEqual(expm1_64(-0x1.a206f0a19dcc4p+2), -0x1.ff4115c03f78dp-1); |
| 343 | try expectEqual(expm1_64(0x1.288bbb0d6a1e6p+3), 0x1.4ab477496e07ep+13); |
| 344 | try expectEqual(expm1_64(0x1.52efd0cd80497p-1), 0x1.e095382100a01p-1); |
| 345 | try expectEqual(expm1_64(-0x1.a05cc754481d1p-2), -0x1.561c3e0582be6p-2); |
| 346 | try expectEqual(expm1_64(0x1.1f9ef934745cbp-1), 0x1.81ec4cd4d4a8fp-1); |
| 347 | try expectEqual(expm1_64(0x1.8c5db097f7442p-1), 0x1.2b3363a944bf7p+0); |
| 348 | try expectEqual(expm1_64(-0x1.5b86ea8118a0ep-1), -0x1.f8951aebffbafp-2); |
| 349 | } |
| 350 | |
| 351 | test "expm1_64() boundary" { |
| 352 | try expectEqual(expm1_64(0x1.62e42fefa39efp+9), 0x1.fffffffffff2ap+1023); // Last value before result is inf |
| 353 | try expectEqual(expm1_64(0x1.62e42fefa39f0p+9), math.inf(f64)); // First value that gives inf |
| 354 | try expectEqual(expm1_64(0x1.fffffffffffffp+1023), math.inf(f64)); // Max input value |
| 355 | try expectEqual(expm1_64(0x1p-1074), 0x1p-1074); // Min positive input value |
| 356 | try expectEqual(expm1_64(-0x1p-1074), -0x1p-1074); // Min negative input value |
| 357 | try expectEqual(expm1_64(0x1p-1022), 0x1p-1022); // First positive subnormal input |
| 358 | try expectEqual(expm1_64(-0x1p-1022), -0x1p-1022); // First negative subnormal input |
| 359 | try expectEqual(expm1_64(0x1.fffffffffffffp-1021), 0x1.fffffffffffffp-1021); // Last positive value before subnormal |
| 360 | try expectEqual(expm1_64(-0x1.fffffffffffffp-1021), -0x1.fffffffffffffp-1021); // Last negative value before subnormal |
| 361 | try expectEqual(expm1_64(-0x1.2b708872320e1p+5), -0x1.fffffffffffffp-1); // Last value before result is -1 |
| 362 | try expectEqual(expm1_64(-0x1.2b708872320e2p+5), -1); // First value where result is -1 |
| 363 | } |