Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
scaled_rational.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <mpfr.h>
5
6#include <algorithm>
7#include <array>
8
9namespace uni20::detail
10{
11// Products and quotient comparisons can have exponents outside MPFR's range.
12// Widen the exponent, not the significand: no large power of two is allocated.
13using scaled_exponent = __int128_t;
14
15struct scaled_rational
16{
17 exact_rational coefficient;
18 scaled_exponent exponent = 0;
19};
20
21inline scaled_rational operator*(scaled_rational const& a, scaled_rational const& b)
22{
23 return {a.coefficient * b.coefficient, a.exponent + b.exponent};
24}
25inline scaled_rational operator-(scaled_rational const& a) { return {-a.coefficient, a.exponent}; }
26
27// Exact sign of a short sum of integer * 2^exponent terms. Only align a lower
28// term when it can affect the sign. Otherwise a huge exponent gap costs no
29// storage. If alignment is necessary, its size is bounded by the coefficient
30// sizes already present (plus two bits for the at-most-four remaining terms).
31template <std::size_t N>
32int scaled_sum_sign(std::array<mpz_srcptr, N> const& coefficients, std::array<scaled_exponent, N> const& exponents)
33{
34 static_assert(N <= 4);
35 std::array<std::size_t, N> order{};
36 std::size_t count = 0;
37 for (std::size_t i = 0; i < N; ++i)
38 if (mpz_sgn(coefficients[i]) != 0) order[count++] = i;
39 if (count == 0) return 0;
40 std::sort(order.begin(), order.begin() + count, [&](auto a, auto b) { return exponents[a] > exponents[b]; });
41 mp_integer accumulator;
42 mpz_set(accumulator.value, coefficients[order[0]]);
43 auto exponent = exponents[order[0]];
44 for (std::size_t j = 1; j < count; ++j)
45 {
46 auto next = order[j];
47 if (mpz_sgn(accumulator.value) == 0)
48 mpz_set(accumulator.value, coefficients[next]);
49 else
50 {
51 auto top = exponents[next] + mpz_sizeinbase(coefficients[next], 2);
52 for (std::size_t k = j + 1; k < count; ++k)
53 top = std::max(top, exponents[order[k]] + mpz_sizeinbase(coefficients[order[k]], 2));
54 auto lower_bound = exponent + mpz_sizeinbase(accumulator.value, 2) - 1;
55 if (lower_bound >= top + 2) return mpz_sgn(accumulator.value);
56 mpz_mul_2exp(accumulator.value, accumulator.value, static_cast<mp_bitcnt_t>(exponent - exponents[next]));
57 mpz_add(accumulator.value, accumulator.value, coefficients[next]);
58 }
59 exponent = exponents[next];
60 }
61 return mpz_sgn(accumulator.value);
62}
63
64// Round (a + b) / (c + d) once. This is the large-exponent path for mixed
65// rational/MPFR arithmetic; the ordinary path continues to use GMP rationals.
66// Exact comparisons locate the binade and significand, including halfway cases,
67// without a Ziv loop that could fail to terminate on an exact rounding boundary.
68class scaled_rational_ratio {
69 public:
70 explicit scaled_rational_ratio(std::array<scaled_rational, 4> const& terms)
71 {
72 mp_integer denominator, factor;
73 mpz_set_ui(denominator.value, 1);
74 for (auto const& term : terms)
75 mpz_lcm(denominator.value, denominator.value, mpq_denref(term.coefficient.native_handle()));
76 for (std::size_t i = 0; i < 4; ++i)
77 {
78 auto q = terms[i].coefficient.native_handle();
79 mpz_divexact(factor.value, denominator.value, mpq_denref(q));
80 mpz_mul(coefficients_[i].value, mpq_numref(q), factor.value);
81 exponents_[i] = terms[i].exponent;
82 }
83 int numerator_sign =
84 scaled_sum_sign<2>({coefficients_[0].value, coefficients_[1].value}, {exponents_[0], exponents_[1]});
85 int denominator_sign =
86 scaled_sum_sign<2>({coefficients_[2].value, coefficients_[3].value}, {exponents_[2], exponents_[3]});
87 if (denominator_sign == 0) throw std::domain_error("scaled rational: zero denominator");
88 sign_ = numerator_sign * denominator_sign;
89 for (std::size_t i = 0; i < 4; ++i)
90 if ((i < 2 ? numerator_sign : denominator_sign) < 0) mpz_neg(coefficients_[i].value, coefficients_[i].value);
91 }
92
93 void round_to(mpfr_ptr out) const
94 {
95 if (sign_ == 0)
96 {
97 mpfr_set_zero(out, 1);
98 return;
99 }
100 mp_integer mantissa, midpoint;
101 mpz_set_ui(mantissa.value, 1);
102 auto const emin = mpfr_get_emin(), emax = mpfr_get_emax();
103 auto const precision = mpfr_get_prec(out);
104 if (this->compare(mantissa.value, emax) >= 0)
105 {
106 mpfr_set_ui_2exp(out, 1, emax, MPFR_RNDN); // overflow, including flags
107 if (sign_ < 0) mpfr_neg(out, out, MPFR_RNDN);
108 return;
109 }
110 if (this->compare(mantissa.value, scaled_exponent(emin) - 1) < 0)
111 {
112 // No subnormals in MPFR. The halfway case rounds toward signed zero.
113 if (this->compare(mantissa.value, scaled_exponent(emin) - 2) <= 0)
114 mpfr_set_zero(out, sign_);
115 else
116 mpfr_set_si_2exp(out, sign_, emin - 1, MPFR_RNDN);
117 // MPFR detects tininess after rounding at an unbounded exponent range.
118 mpz_mul_2exp(midpoint.value, mantissa.value, precision + 1);
119 mpz_sub_ui(midpoint.value, midpoint.value, 1);
120 if (this->compare(midpoint.value, scaled_exponent(emin) - precision - 2) < 0) mpfr_set_underflow();
121 mpfr_set_inexflag();
122 return;
123 }
124 // Largest e such that 2^e <= |value|. Binary search needs only logarithmic
125 // work in the exponent range, even for values such as 2^1000000000.
126 scaled_exponent lo = scaled_exponent(emin) - 1, hi = emax;
127 while (hi - lo > 1)
128 {
129 auto mid = lo + (hi - lo) / 2;
130 if (this->compare(mantissa.value, mid) >= 0)
131 lo = mid;
132 else
133 hi = mid;
134 }
135 auto exponent = lo - precision + 1;
136 mpz_mul_2exp(mantissa.value, mantissa.value, precision - 1);
137 for (mpfr_prec_t bit = precision - 1; bit-- > 0;)
138 {
139 mpz_setbit(mantissa.value, bit);
140 if (this->compare(mantissa.value, exponent) < 0) mpz_clrbit(mantissa.value, bit);
141 }
142 bool const inexact = this->compare(mantissa.value, exponent) != 0;
143 if (inexact)
144 {
145 mpz_mul_2exp(midpoint.value, mantissa.value, 1);
146 mpz_add_ui(midpoint.value, midpoint.value, 1);
147 int comparison = this->compare(midpoint.value, exponent - 1);
148 if (comparison > 0 || (comparison == 0 && mpz_odd_p(mantissa.value)))
149 mpz_add_ui(mantissa.value, mantissa.value, 1);
150 }
151 if (sign_ < 0) mpz_neg(mantissa.value, mantissa.value);
152 mpfr_set_z_2exp(out, mantissa.value, static_cast<mpfr_exp_t>(exponent), MPFR_RNDN);
153 if (inexact) mpfr_set_inexflag();
154 }
155
156 private:
157 // Compare the positive magnitude with candidate * 2^exponent, exactly.
158 int compare(mpz_srcptr candidate, scaled_exponent exponent) const
159 {
160 mp_integer c, d;
161 mpz_mul(c.value, coefficients_[2].value, candidate);
162 mpz_mul(d.value, coefficients_[3].value, candidate);
163 mpz_neg(c.value, c.value);
164 mpz_neg(d.value, d.value);
165 return scaled_sum_sign<4>({coefficients_[0].value, coefficients_[1].value, c.value, d.value},
166 {exponents_[0], exponents_[1], exponents_[2] + exponent, exponents_[3] + exponent});
167 }
168 std::array<mp_integer, 4> coefficients_;
169 std::array<scaled_exponent, 4> exponents_{};
170 int sign_ = 0;
171};
172} // namespace uni20::detail
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
Definition cli.cpp:6