LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
fdlibm.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_FDLIBM_H
6#define LINE_UTIL_FDLIBM_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * The fdlibm elementary functions Java specifies, reproduced, for the code
12 * paths whose whole purpose is to land on the same bits as a Java reference.
13 *
14 * WHY THIS FILE EXISTS: libm IS NOT A FIXED FUNCTION. `std::log1p` is allowed a
15 * 1-ulp error and glibc changed which representative it returns between 2.35
16 * and 2.39 (Ubuntu 22.04 vs 24.04). Measured over 200k draws, `log1p` and
17 * `expm1` differ between those two while `log`, `exp`, `pow`, `sqrt`, `lgamma`
18 * and `tgamma` agree. In ordinary numerics a last-bit difference is noise, but
19 * a SEEDED discrete-event simulation is a chaotic map of its variates: one ulp
20 * on one interarrival reorders the event queue and the whole sample path parts
21 * company. That is not hypothetical -- the same `common/ldes` binary, the same
22 * model.json and the same `-s 100000 --seed 23000` gave QLen 98.565592 on a
23 * glibc-2.35 host and 98.562490 under the containerized MATLAB's glibc 2.39,
24 * against an ABSOLUTE 1e-3 gate on the recorded baseline.
25 *
26 * WHY fdlibm IS THE RIGHT TARGET AND NOT MERELY A STABLE ONE. `StrictMath` IS
27 * fdlibm by specification, and `Math.log1p` was measured to agree with it on
28 * every one of 400k draws. Hashing raw bit patterns so the comparison is
29 * language-neutral, this implementation is bit-identical to both:
30 *
31 * C, host glibc 2.35 16873585727775930126
32 * C, container glibc 2.39 14249616856320735164
33 * C, this file 12005405864943986042
34 * Java Math.log1p 12005405864943986042
35 * Java StrictMath.log1p 12005405864943986042
36 *
37 * So routing the SSJ variate layer here does two things at once: it makes the
38 * engine reproduce itself on any glibc, and it moves it ONTO the Java engine's
39 * arithmetic rather than beside it. Only the seeded sample-path code should
40 * call these -- the analytical APIs are free to use libm, where a last bit does
41 * not cascade.
42 *
43 * Source: Sun's fdlibm s_log1p.c, the algorithm `StrictMath.log1p` is defined
44 * to use. Transcribed with its constants and branch structure intact.
45 */
46
47#include <cmath>
48#include <cstdint>
49#include <cstring>
50#include <limits>
51
52namespace line {
53namespace fdlibm {
54
55namespace detail {
56
57inline int hi_word(double x) {
58 std::int64_t i;
59 std::memcpy(&i, &x, sizeof i);
60 return static_cast<int>(i >> 32);
61}
62
63inline void set_hi_word(double* x, int hi) {
64 std::int64_t i;
65 std::memcpy(&i, x, sizeof i);
66 i = (static_cast<std::int64_t>(hi) << 32) | (i & 0xffffffffLL);
67 std::memcpy(x, &i, sizeof i);
68}
69
70} // namespace detail
71
72/**
73 * log(1+x), fdlibm's representative -- the one StrictMath.log1p returns.
74 *
75 * The argument reduction writes 1+x = 2^k (1+f) with f in [sqrt(2)/2, sqrt(2)),
76 * carries the rounding of that sum in the correction term c, and evaluates
77 * log(1+f) from the odd series in s = f/(2+f). The magic constants are the
78 * high words of the branch points: 0x3FDA827A is sqrt(2)/2 - 1 and 0x6a09e is
79 * the mantissa of sqrt(2).
80 */
81inline double log1p(double x) {
82 static const double ln2_hi = 6.93147180369123816490e-01;
83 static const double ln2_lo = 1.90821492927058770002e-10;
84 static const double two54 = 1.80143985094819840000e+16;
85 static const double Lp1 = 6.666666666666735130e-01;
86 static const double Lp2 = 3.999999999940941908e-01;
87 static const double Lp3 = 2.857142874366239149e-01;
88 static const double Lp4 = 2.222219843214978396e-01;
89 static const double Lp5 = 1.818357216161805012e-01;
90 static const double Lp6 = 1.531383769920937332e-01;
91 static const double Lp7 = 1.479819860511658591e-01;
92
93 double hfsq, f = 0.0, c = 0.0, s, z, R, u;
94 int k, hx, hu = 0, ax;
95
96 hx = detail::hi_word(x);
97 ax = hx & 0x7fffffff;
98 k = 1;
99 if (hx < 0x3FDA827A) { // x < 0.41422
100 if (ax >= 0x3ff00000) { // x <= -1.0
101 if (x == -1.0) return -two54 / 0.0; // -inf
102 return (x - x) / (x - x); // NaN
103 }
104 if (ax < 0x3e200000) { // |x| < 2**-29
105 if (two54 + x > 0.0 && ax < 0x3c900000) return x;
106 return x - x * x * 0.5;
107 }
108 if (hx > 0 || hx <= static_cast<int>(0xbfd2bec3)) {
109 k = 0;
110 f = x;
111 hu = 1;
112 }
113 }
114 if (hx >= 0x7ff00000) return x + x; // inf or NaN
115 if (k != 0) {
116 if (hx < 0x43400000) {
117 u = 1.0 + x;
118 hu = detail::hi_word(u);
119 k = (hu >> 20) - 1023;
120 // The correction term recovers what 1+x rounded away.
121 c = (k > 0) ? 1.0 - (u - x) : x - (u - 1.0);
122 c /= u;
123 } else {
124 u = x;
125 hu = detail::hi_word(u);
126 k = (hu >> 20) - 1023;
127 c = 0.0;
128 }
129 hu &= 0x000fffff;
130 if (hu < 0x6a09e) {
131 detail::set_hi_word(&u, hu | 0x3ff00000);
132 } else {
133 k += 1;
134 detail::set_hi_word(&u, hu | 0x3fe00000);
135 hu = (0x00100000 - hu) >> 2;
136 }
137 f = u - 1.0;
138 }
139
140 hfsq = 0.5 * f * f;
141 if (hu == 0) { // |f| < 2**-20
142 if (f == 0.0) {
143 if (k == 0) return 0.0;
144 c += k * ln2_lo;
145 return k * ln2_hi + c;
146 }
147 R = hfsq * (1.0 - 0.66666666666666666 * f);
148 if (k == 0) return f - R;
149 return k * ln2_hi - ((R - (k * ln2_lo + c)) - f);
150 }
151 s = f / (2.0 + f);
152 z = s * s;
153 R = z * (Lp1 + z * (Lp2 + z * (Lp3 + z * (Lp4 + z * (Lp5 + z * (Lp6 + z * Lp7))))));
154 if (k == 0) return f - (hfsq - s * (hfsq + R));
155 return k * ln2_hi - ((hfsq - (s * (hfsq + R) + (k * ln2_lo + c))) - f);
156}
157
158} // namespace fdlibm
159} // namespace line
160
161#endif // LINE_UTIL_FDLIBM_H