// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once // exp in single precision, the same bits wherever it is compiled: the host compiler (no FMA contraction, // see CMakeLists.txt) and the Metal compiler (fast math off) both see only IEEE +, -, * rounded to // nearest and exact scaling by a power of two, so the profile fit's Gaussian is the same on the CPU and // on an Apple GPU, whose exp no host function reproduces. It is closer to correctly rounded than macOS's // expf: at most 0.53 ulp for a normal result, and away from a correctly rounded float for 0.2% of // arguments below -1 and 0.004% in [-1, 0]. // // exp(x) = 2^k exp(r), k = rint(x / ln2), r = x - k ln2 with ln2 split three ways, the first two parts // of 16 bits so that k times them is exact (Cody & Waite). r is carried as rr + re, rr^2 is formed exactly // as zh + zl (Dekker's product with Veltkamp's split) and the sums into 1 + rr + rr^2/2 are error-free // (2Sum, Fast2Sum), so the only rounding that matters is the last one. The rest of the series is a // degree-4 minimax fit, relative error 1.3e-10. #ifdef __METAL_VERSION__ inline float BraggBitsToFloat(int i) { return as_type(i); } inline int BraggFloatToBits(float f) { return as_type(f); } #else #include #include inline float BraggBitsToFloat(int32_t i) { return std::bit_cast(i); } inline int32_t BraggFloatToBits(float f) { return std::bit_cast(f); } #endif // p * 2^k, rounded once where the result is subnormal: below k = -125 the scaling is split in two. The // two cases differ only in their operands - the normal one scales by 1 the second time, which is exact - // so a loop over it has no branch and vectorises. inline float BraggScale2(float p, int k) { const bool subnormal = k < -125; const float first = BraggBitsToFloat(((subnormal ? k + 100 : k) + 127) << 23); const float second = subnormal ? BraggBitsToFloat((-100 + 127) << 23) : 1.0f; return (p * first) * second; } // Following Cody & Waite (1980) Software Manual for the Elementary Functions; Dekker (1971) Numer. Math. // 18, 224-242; Knuth, The Art of Computer Programming Vol. 2, 4.2.2 (2Sum). // The two ends are told apart on the bits of x and applied to the bits of the result, with integer masks // rather than branches or selects: the compiler moves arithmetic behind a branch or a select that only // one side needs, and may not run float arithmetic on a path that does not need it, which either way // keeps a loop over BraggExp from vectorising. Out of range the arithmetic runs on 0 and its result is // masked off; in range it is the same arithmetic on the same x. (A negative float's bits are a negative // integer that grows as the float falls; a positive one's grow with it.) inline float BraggExp(float x_in) { const int bits = BraggFloatToBits(x_in); const bool under = (bits < 0) & (bits > BraggFloatToBits(-104.0f)); // below half the smallest subnormal const bool over = bits > BraggFloatToBits(88.72283f); // overflows const int in_range = -static_cast(!(under | over)); // every bit set, or none const float x = BraggBitsToFloat(bits & in_range); const float shifter = 12582912.0f; // 1.5 * 2^23: adding it rounds to an integer const float kf = (x * 1.44269504f + shifter) - shifter; const int k = static_cast(kf); const float r1 = x - kf * 0.693145751953125f; // exact const float t2 = kf * 1.4286197256296873e-06f; // exact const float rr = r1 - t2; // r1 - t2 = rr + e2 (2Sum) const float bv0 = rr - r1; const float e2 = (r1 - (rr - bv0)) + (-t2 - bv0); const float re = e2 - kf * -1.2905320041778356e-11f; const float sp = rr * 4097.0f; // rr^2 = zh + zl const float rh = sp - (sp - rr); const float rl = rr - rh; const float zh = rr * rr; const float zl = ((rh * rh - zh) + (rh * rl + rh * rl)) + rl * rl; const float h = 0.1666666567325592f + rr * (0.04166644066572189f + rr * (0.008333569392561913f + rr * (0.0013937620678916574f + rr * 0.0001974606711883098f))); const float c = 0.5f * zl + ((re + re * rr) + (zh * rr) * h); const float hz = 0.5f * zh; // rr + zh/2 = s1 + e1 (2Sum) const float s1 = rr + hz; const float bv1 = s1 - rr; const float e1 = (rr - (s1 - bv1)) + (hz - bv1); const float t1 = 1.0f + s1; // 1 + s1 = t1 + e3 (Fast2Sum, |s1| < 1) const float e3 = (1.0f - t1) + s1; const float e = BraggScale2(t1 + (e3 + (e1 + c)), k); return BraggBitsToFloat((BraggFloatToBits(e) & in_range) | (static_cast(over) * 0x7f800000)); }