Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
native_upper_gamma_guard.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace uni20::detail::native_math::upper_gamma_guard
6{
7// Two native words retain guard bits through cancellation and final range
8// decisions. No MPFR or wider scalar type is used. See native_upper_gamma.md.
9template <NativeReal R> struct two_word
10{
11 R hi = 0, lo = 0;
12 two_word() = default;
13 two_word(R x) : hi(x) {}
14 two_word(R h, R l) : hi(h), lo(l) {}
15 static two_word sum(R a, R b)
16 {
17 R s = a + b, v = s - a;
18 return {s, (a - (s - v)) + (b - v)};
19 }
20 friend two_word operator+(two_word a, two_word b)
21 {
22 auto s = two_word::sum(a.hi, b.hi);
23 auto t = two_word::sum(a.lo, b.lo);
24 auto u = two_word::sum(s.lo, t.hi);
25 auto v = two_word::sum(s.hi, u.hi);
26 return two_word::sum(v.hi, v.lo + u.lo + t.lo);
27 }
28 friend two_word operator-(two_word a) { return {-a.hi, -a.lo}; }
29 friend two_word operator-(two_word a, two_word b) { return a + -b; }
30 friend two_word operator*(two_word a, two_word b)
31 {
32 R p = a.hi * b.hi;
33 R e = std::fma(a.hi, b.hi, -p);
34 e = std::fma(a.hi, b.lo, e);
35 e = std::fma(a.lo, b.hi, e);
36 e = std::fma(a.lo, b.lo, e);
37 return two_word::sum(p, e);
38 }
39 friend two_word operator/(two_word a, two_word b)
40 {
41 R q = a.hi / b.hi;
42 two_word result(q), r = a - b * result;
43 R t = r.hi / b.hi;
44 result = result + two_word(t);
45 r = a - b * result;
46 return result + two_word(r.hi / b.hi);
47 }
48 two_word scaled(int n) const { return {std::ldexp(hi, n), std::ldexp(lo, n)}; }
49 R rounded() const { return hi + lo; }
50};
51// Leave room for accumulated two-word roundoff: 40/85/96/145 working bits.
52template <NativeReal R> R epsilon()
53{
54 constexpr int p = numeric_limits<R>::digits();
55 return std::ldexp(R(1), -p - std::min(p - 8, 32));
56}
57template <NativeReal R> two_word<R> log_two()
58{
59 // Generated by scripts/generate-upper-gamma-bounds.py. Each 16-bit chunk
60 // converts exactly even at native float32 precision.
61 constexpr unsigned chunks[] = {0xb172, 0x17f7, 0xd1cf, 0x79ab, 0xc9e3, 0xb398, 0x03f2, 0xf6af,
62 0x40f3, 0x4326, 0x7298, 0xb62d, 0x8a0d, 0x175b, 0x8baa, 0xfa2b};
63 two_word<R> s;
64 constexpr int n = (2 * numeric_limits<R>::digits() + 15) / 16;
65 for (int i = 0; i < n; ++i)
66 s = s + two_word<R>(std::ldexp(R(chunks[i]), -16 * (i + 1)));
67 return s;
68}
69template <NativeReal R> two_word<R> log(R x)
70{
71 using P = two_word<R>;
72 int e;
73 R m = std::frexp(x, &e);
74 if (m < R(.75))
75 {
76 m *= 2;
77 --e;
78 }
79 P t = (P(m) - P(1)) / (P(m) + P(1));
80 P t2 = t * t, term = t, s = t;
81 for (int k = 1; k < 2 * numeric_limits<R>::digits(); ++k)
82 {
83 term = term * t2;
84 P v = term / P(R(2 * k + 1));
85 s = s + v;
86 if (std::abs(v.hi) < epsilon<R>() * std::abs(s.hi) / 16) break;
87 }
88 return P(2) * s + P(R(e)) * log_two<R>();
89}
90template <NativeReal R> R exp(two_word<R> v)
91{
92 using L = numeric_limits<R>;
93 using P = two_word<R>;
94 if (v.hi > R(2 * L::max_exponent)) return L::infinity();
95 if (v.hi < R(2 * (L::min_exponent - L::digits()))) return R(0);
96 P ln2 = log_two<R>();
97 int n = int(std::nearbyint(v.hi / ln2.hi));
98 P r = v - P(R(n)) * ln2, s(1), t(1);
99 for (int k = 1; k < 2 * L::digits(); ++k)
100 {
101 t = t * r / P(R(k));
102 s = s + t;
103 if (std::abs(t.hi) < epsilon<R>() / 16) break;
104 }
105 // Round subnormal results in integer units, before a native mantissa
106 // rounding can erase the deciding low word.
107 constexpr int denorm_exp = L::min_exponent - L::digits();
108 if (n < L::min_exponent)
109 {
110 P units = s.scaled(n - denorm_exp);
111 R q = std::floor(units.hi);
112 P remainder = units - P(q);
113 if (remainder.hi < 0)
114 {
115 q -= 1;
116 remainder = remainder + P(1);
117 }
118 P half = remainder - P(R(.5));
119 if (half.hi > 0 || (half.hi == 0 && half.lo > 0) || (half.hi == 0 && half.lo == 0 && std::fmod(q, R(2)) != 0))
120 q += 1;
121 return std::ldexp(q, denorm_exp);
122 }
123 return std::ldexp(s.rounded(), n);
124}
125// Contracted upper-Gamma fraction; positive-input integral bounds justify
126// the small-x limiting shortcuts before iteration.
127template <NativeReal R> two_word<R> fraction(R av, R xv)
128{
129 using P = two_word<R>;
130 P a(av), x(xv), previous;
131 R eps = epsilon<R>();
132 if (av < -1 && xv / (-av - 1) < eps / 4) return P(1) / (-a);
133 if (av <= R(-.5) && xv < eps * eps / 64) return P(1) / (-a);
134 // For b=-a>0, U=integral exp(-x*t)/(1+t)^(b+1) dt. Integrating
135 // Taylor's remainder bounds the error after n terms by the next term,
136 // x^n/[b*(b-1)*...*(b-n)], provided n<b. This converges quickly near
137 // a small-x range boundary where the fraction can converge too slowly.
138 if (av < -1 && xv <= 1)
139 {
140 P term = P(1) / (-a), sum = term;
141 for (int n = 1; n < 2 * numeric_limits<R>::digits() && R(n) < -av; ++n)
142 {
143 P next = -term * x / (-a - P(R(n)));
144 if (std::abs(next.hi) <= eps * std::abs(sum.hi) / 16) return sum;
145 if (std::abs(next.hi) >= std::abs(term.hi)) break;
146 sum = sum + next;
147 term = next;
148 }
149 }
150 for (int depth = 8; depth <= 128 * numeric_limits<R>::digits(); depth *= 2)
151 {
152 P value;
153 for (int k = depth; k >= 1; --k)
154 value = (a - P(R(k))) * (P(R(k)) / (x - a + P(R(2 * k + 1)) + value));
155 value = P(1) / (x - a + P(1) + value);
156 if (std::abs((value - previous).hi) <= eps * std::abs(value.hi)) return value;
157 previous = value;
158 }
159 return P(numeric_limits<R>::quiet_NaN());
160}
161template <NativeReal R> R upper_gamma(R a, R x)
162{
163 using P = two_word<R>;
164 using L = numeric_limits<R>;
165 P lx = log(x);
166 int shift;
167 std::frexp(std::max(R(1), std::abs(lx.hi)), &shift);
168 // Keep both terms below the native range before the two-word product:
169 // a finite fused difference does not imply that its product is finite.
170 // Decide the range while scaled, then restore the bounded exponent.
171 P half = P(a / 2) * lx.scaled(-shift) - P(std::ldexp(x / 2, -shift));
172 R limit = std::ldexp(R(4 * L::max_exponent), -shift);
173 if (half.hi > limit) return L::infinity();
174 if (half.hi < -limit) return R(0);
175 half = half.scaled(shift);
176 P u = fraction(a, x);
177 if (std::isnan(u.hi)) return u.hi;
178 P lu = log(u.hi) + P(u.lo) / P(u.hi);
179 return exp(P(2) * half + lu);
180}
181template <NativeReal R> two_word<R> log_pair(two_word<R> x)
182{
183 return log(x.hi) + two_word<R>(x.lo) / two_word<R>(x.hi);
184}
185template <NativeReal R> two_word<R> log_two_pi()
186{
187 constexpr unsigned chunks[] = {0xd67f, 0x1c86, 0x4beb, 0x4a69, 0x2979, 0x2002, 0x8832, 0x4047,
188 0x9f61, 0x1f1a, 0x268b, 0x169b, 0xbd8d, 0x4626, 0x7b54, 0x2aba};
189 two_word<R> s(R(1));
190 constexpr int n = (2 * numeric_limits<R>::digits() + 15) / 16;
191 for (int i = 0; i < n; ++i)
192 s = s + two_word<R>(std::ldexp(R(chunks[i]), -16 * (i + 1)));
193 return s;
194}
195template <NativeReal R> two_word<R> log_gamma(R a)
196{
197 using P = two_word<R>;
198 constexpr int p = numeric_limits<R>::digits(),
199 cutoff = p <= 24 ? 32
200 : p <= 64 ? 64
201 : 128,
202 terms = p <= 24 ? 4
203 : p <= 53 ? 8
204 : p <= 64 ? 10
205 : 16;
206 constexpr long long nums[] = {1,
207 -1,
208 1,
209 -1,
210 1,
211 -691,
212 1,
213 -3617,
214 43867,
215 -174611,
216 77683,
217 -236364091LL,
218 657931,
219 -3392780147LL,
220 1723168255201LL,
221 -7709321041217LL};
222 constexpr long long dens[] = {12, 360, 1260, 1680, 1188, 360360, 156, 122400,
223 244188, 125400, 5796, 1506960, 300, 93960, 2492028, 505920};
224 P z(a), prefix;
225 while (z.hi < cutoff)
226 {
227 prefix = prefix + log_pair(z);
228 z = z + P(1);
229 }
230 P inverse = P(1) / z, square = inverse * inverse, c = P(R(nums[terms - 1])) / P(R(dens[terms - 1]));
231 for (int k = terms - 2; k >= 0; --k)
232 c = c * square + P(R(nums[k])) / P(R(dens[k]));
233 return (z - P(R(.5))) * log_pair(z) - z + log_two_pi<R>() / P(2) + inverse * c - prefix;
234}
235template <NativeReal R> two_word<R> exp_pair(two_word<R> v)
236{
237 using L = numeric_limits<R>;
238 using P = two_word<R>;
239 if (v.hi < R(2 * (L::min_exponent - L::digits()))) return P(0);
240 P ln2 = log_two<R>();
241 int n = int(std::nearbyint(v.hi / ln2.hi));
242 P r = v - P(R(n)) * ln2, s(1), t(1);
243 for (int k = 1; k < 2 * L::digits(); ++k)
244 {
245 t = t * r / P(R(k));
246 s = s + t;
247 if (std::abs(t.hi) < epsilon<R>() / 16) break;
248 }
249 return s.scaled(n);
250}
251template <NativeReal R> R lower_complement(R av, R xv)
252{
253 using P = two_word<R>;
254 using L = numeric_limits<R>;
255 P a(av), x(xv), g = log_gamma(av), sum(1), term(1);
256 for (int k = 1; k <= 128 * L::digits(); ++k)
257 {
258 term = term * x / (a + P(R(k)));
259 sum = sum + term;
260 if (std::abs(term.hi) <= epsilon<R>() * std::abs(sum.hi) / 16)
261 {
262 P lower = exp_pair(a * log(xv) - x - log(av) - g) * sum;
263 return exp(g + log_pair(P(1) - lower));
264 }
265 }
266 return L::quiet_NaN();
267}
268
269} // namespace uni20::detail::native_math::upper_gamma_guard
AsyncTask square(ReadBuffer< int > in, WriteBuffer< int > out)
Definition async_tbb_reduction_example.cpp:39
locale_t value
Definition data_table.cpp:49
auto operator-(Async< T > const &x)
Unary negation overload for Async values.
Definition async_ops.hpp:477
Var< T > operator*(T x, Var< T > y)
Computes x * y with reverse-mode gradient propagation.
Definition var_toys.hpp:176
Var< T > operator+(Var< T > x, T y)
Computes x + y with reverse-mode gradient propagation.
Definition var_toys.hpp:136
Definition cli.cpp:6
constexpr detail::scalar_math::exp_fn exp
Natural exponential with scalar-specific precision handling.
Definition math.hpp:465
constexpr detail::scalar_math::upper_gamma_fn upper_gamma
Upper unregularized incomplete Gamma, with shape and lower integration endpoint.
Definition math.hpp:593
constexpr detail::scalar_math::log_fn log
Natural logarithm with scalar-specific precision handling.
Definition math.hpp:467
Definition data_table.cpp:8
void sum(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a full sum into an explicit async scalar tensor.
Definition linalg/async/reductions.hpp:309
constexpr auto log_two
Natural logarithm of two, with the same type and precision rules as pi.
Definition math_constants.hpp:42
mpreal remainder(mpreal const &a, mpreal const &b)
Nearest-even-quotient remainder; exact operands retain exact rational results.
Definition mpreal_functions.hpp:534
constexpr auto operator/(basic_half_int< T > lhs, U rhs) -> U
Divide a half-integer value by a floating-point scalar.
Definition half_int.hpp:422