Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
exact_rational.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "precision.hpp"
4#include <charconv>
5#include <concepts>
6#include <gmp.h>
7#include <limits>
8#include <optional>
9#include <string>
10#include <string_view>
11#include <type_traits>
12#include <utility>
13
14namespace uni20
15{
16class mpreal;
17class exact_rational;
18class decimal_literal;
19namespace literals
20{
21constexpr decimal_literal operator""_mp(char const* text);
22}
23
28 public:
30 mpreal at(Precision precision) const;
31 constexpr bool is_exact() const noexcept { return true; }
32 constexpr std::string_view spelling() const noexcept { return spelling_; }
33
34 private:
35 friend constexpr decimal_literal literals::operator""_mp(char const* text);
36 explicit constexpr decimal_literal(char const* text) : spelling_(text) {}
37 std::string_view spelling_;
38};
39
40namespace detail
41{
42struct mpreal_access;
43// Own temporary GMP integers, including on parser exceptions.
44struct mp_integer
45{
46 mpz_t value;
47 mp_integer() { mpz_init(value); }
48 ~mp_integer() { mpz_clear(value); }
49 mp_integer(mp_integer const&) = delete;
50 mp_integer& operator=(mp_integer const&) = delete;
51};
52
53inline void parse_decimal_rational(mpq_ptr result, std::string_view text)
54{
55 if (text.empty()) throw std::invalid_argument("exact_rational: empty decimal");
56 std::string unseparated;
57 if (text.find('\'') != std::string_view::npos)
58 {
59 for (std::size_t i = 0; i < text.size(); ++i)
60 {
61 if (text[i] != '\'')
62 unseparated += text[i];
63 else if (i == 0 || i + 1 == text.size() || text[i - 1] < '0' || text[i - 1] > '9' || text[i + 1] < '0' ||
64 text[i + 1] > '9')
65 throw std::invalid_argument("exact_rational: misplaced digit separator");
66 }
67 text = unseparated;
68 }
69 std::string digits;
70 std::size_t pos = 0;
71 bool const negative = text[pos] == '-';
72 if (negative || text[pos] == '+') ++pos;
73 bool dot = false;
74 std::size_t fraction_digits = 0;
75 for (; pos < text.size(); ++pos)
76 {
77 char c = text[pos];
78 if (c == '.')
79 {
80 if (dot) throw std::invalid_argument("exact_rational: repeated decimal point");
81 dot = true;
82 }
83 else if (c >= '0' && c <= '9')
84 {
85 digits += c;
86 if (dot) ++fraction_digits;
87 }
88 else
89 break;
90 }
91 if (digits.empty()) throw std::invalid_argument("exact_rational: expected decimal digits");
92 // Decimal literals are deliberate: hexadecimal/binary spellings are not accepted.
93 bool exponent_negative = false;
94 unsigned long exponent = 0;
95 if (pos < text.size() && (text[pos] == 'e' || text[pos] == 'E'))
96 {
97 ++pos;
98 if (pos < text.size() && (text[pos] == '+' || text[pos] == '-')) exponent_negative = text[pos++] == '-';
99 auto const first = text.data() + pos;
100 auto const last = text.data() + text.size();
101 auto const parsed = std::from_chars(first, last, exponent);
102 if (parsed.ec == std::errc::result_out_of_range)
103 throw std::out_of_range("exact_rational: decimal exponent is too large");
104 if (parsed.ec != std::errc{} || parsed.ptr != last)
105 throw std::invalid_argument("exact_rational: invalid decimal exponent");
106 pos = text.size();
107 }
108 if (pos != text.size()) throw std::invalid_argument("exact_rational: invalid decimal spelling");
109 if (fraction_digits > std::numeric_limits<unsigned long>::max())
110 throw std::out_of_range("exact_rational: too many fractional digits");
111 auto const fraction = static_cast<unsigned long>(fraction_digits);
112 if (exponent_negative && exponent > std::numeric_limits<unsigned long>::max() - fraction)
113 throw std::out_of_range("exact_rational: decimal scale is too large");
114 bool const denominator = exponent_negative || fraction > exponent;
115 auto const scale =
116 exponent_negative ? fraction + exponent : (denominator ? fraction - exponent : exponent - fraction);
117 // Bound expansion of short inputs such as 1e999999999 before allocating 10^scale.
118 constexpr unsigned long max_decimal_scale = 1'000'000;
119 if (scale > max_decimal_scale) throw std::out_of_range("exact_rational: decimal scale exceeds 1000000");
120 mpz_set_str(mpq_numref(result), digits.c_str(), 10);
121 mpz_set_ui(mpq_denref(result), 1);
122 if (mpz_sgn(mpq_numref(result)) == 0) return;
123 mp_integer factor;
124 mpz_ui_pow_ui(factor.value, 10, scale);
125 if (denominator)
126 mpz_set(mpq_denref(result), factor.value);
127 else
128 mpz_mul(mpq_numref(result), mpq_numref(result), factor.value);
129 if (negative) mpz_neg(mpq_numref(result), mpq_numref(result));
130 mpq_canonicalize(result);
131}
132// Fractions use signed base-ten integers, with no whitespace or separators.
133inline void parse_rational(mpq_ptr result, std::string_view text)
134{
135 auto slash = text.find('/');
136 if (slash == std::string_view::npos) return parse_decimal_rational(result, text);
137 auto integer = [](mpz_ptr out, std::string_view part) {
138 if (part.empty()) throw std::invalid_argument("exact_rational: missing fraction component");
139 bool negative = part.front() == '-';
140 if (negative || part.front() == '+') part.remove_prefix(1);
141 if (part.empty() || part.find_first_not_of("0123456789") != std::string_view::npos)
142 throw std::invalid_argument("exact_rational: expected integer fraction components");
143 std::string owned(part);
144 mpz_set_str(out, owned.c_str(), 10);
145 if (negative) mpz_neg(out, out);
146 };
147 integer(mpq_numref(result), text.substr(0, slash));
148 integer(mpq_denref(result), text.substr(slash + 1));
149 if (mpz_sgn(mpq_denref(result)) == 0) throw std::domain_error("exact_rational: zero denominator");
150 mpq_canonicalize(result);
151}
152} // namespace detail
153
162 public:
164 {
165 mpq_init(value_);
166 initialized_ = true;
167 }
169 constexpr bool initialized() const noexcept { return initialized_; }
171 constexpr bool is_exact() const noexcept { return this->initialized(); }
172 explicit exact_rational(std::string_view text) : exact_rational() { detail::parse_rational(value_, text); }
173 exact_rational(decimal_literal literal) : exact_rational(literal.spelling()) {}
174
175 template <std::integral I>
176 requires(!std::same_as<I, bool>)
178 {
179 char text[std::numeric_limits<I>::digits10 + 4];
180 auto const result = std::to_chars(text, text + sizeof(text), value);
181 *result.ptr = '\0';
182 mpz_set_str(mpq_numref(value_), text, 10);
183 }
184
186 {
187 if (other.initialized_)
188 {
189 mpq_init(value_);
190 initialized_ = true;
191 mpq_set(value_, other.value_);
192 }
193 }
195 exact_rational(exact_rational&& other) noexcept { this->swap(other); }
197 {
198 this->swap(other);
199 return *this;
200 }
202 {
203 if (initialized_) mpq_clear(value_);
204 }
206 void swap(exact_rational& other) noexcept
207 {
208 // Swap descriptors, not GMP values: an unset descriptor is not an mpq.
209 std::swap(value_[0], other.value_[0]);
210 std::swap(initialized_, other.initialized_);
211 }
212
214 mpreal at(Precision precision) const;
215
218 mpq_srcptr native_handle() const
219 {
220 if (!this->initialized()) throw std::logic_error("exact_rational: numerical use of an unset value");
221 return value_;
222 }
223
225 std::string to_string() const
226 {
227 auto value = this->native_handle();
228 std::string text(mpz_sizeinbase(mpq_numref(value), 10) + mpz_sizeinbase(mpq_denref(value), 10) + 4, '\0');
229 mpq_get_str(text.data(), 10, value);
230 text.resize(std::char_traits<char>::length(text.c_str()));
231 return text;
232 }
233
234 friend exact_rational operator+(exact_rational const& a, exact_rational const& b);
235 friend std::optional<exact_rational> rational_root(exact_rational const&, unsigned long);
236 friend exact_rational operator-(exact_rational const& a, exact_rational const& b);
237 friend exact_rational operator*(exact_rational const& a, exact_rational const& b);
238 friend exact_rational operator/(exact_rational const& a, exact_rational const& b);
239 friend exact_rational operator-(exact_rational const& a);
240
241 private:
242 friend struct detail::mpreal_access;
243 mpq_t value_{};
244 bool initialized_ = false;
245};
246
247inline exact_rational operator+(exact_rational const& a, exact_rational const& b)
248{
249 exact_rational result;
250 mpq_add(result.value_, a.native_handle(), b.native_handle());
251 return result;
252}
254{
255 exact_rational result;
256 mpq_sub(result.value_, a.native_handle(), b.native_handle());
257 return result;
258}
260{
261 exact_rational result;
262 mpq_mul(result.value_, a.native_handle(), b.native_handle());
263 return result;
264}
266{
267 auto divisor = b.native_handle();
268 if (mpq_sgn(divisor) == 0) throw std::domain_error("exact_rational: division by zero");
269 exact_rational result;
270 mpq_div(result.value_, a.native_handle(), divisor);
271 return result;
272}
274{
275 exact_rational result;
276 mpq_neg(result.value_, a.native_handle());
277 return result;
278}
279inline exact_rational operator+(exact_rational const& a)
280{
281 (void)a.native_handle();
282 return a;
283}
284inline bool operator==(exact_rational const& a, exact_rational const& b)
285{
286 return mpq_equal(a.native_handle(), b.native_handle()) != 0;
287}
288inline std::strong_ordering operator<=>(exact_rational const& a, exact_rational const& b)
289{
290 return mpq_cmp(a.native_handle(), b.native_handle()) <=> 0;
291}
292
296inline std::optional<exact_rational> rational_root(exact_rational const& x, unsigned long degree)
297{
298 if (degree == 0) throw std::domain_error("exact_rational: zeroth root");
299 auto q = x.native_handle();
300 bool negative = mpq_sgn(q) < 0;
301 if (negative && degree % 2 == 0) return std::nullopt;
302 detail::mp_integer magnitude;
303 mpz_abs(magnitude.value, mpq_numref(q));
304 exact_rational result;
305 if (!mpz_root(mpq_numref(result.value_), magnitude.value, degree) ||
306 !mpz_root(mpq_denref(result.value_), mpq_denref(q), degree))
307 return std::nullopt;
308 if (negative) mpz_neg(mpq_numref(result.value_), mpq_numref(result.value_));
309 return result;
310}
311
312namespace detail
313{
314// Repeated squaring also accepts exponents wider than an unsigned long.
315template <class T> T integer_power(T base, mpz_srcptr exponent)
316{
317 mp_integer remaining;
318 mpz_abs(remaining.value, exponent);
319 T result{1};
320 if (mpz_sgn(exponent) < 0) base = result / base;
321 while (mpz_sgn(remaining.value) != 0)
322 {
323 if (mpz_odd_p(remaining.value)) result = result * base;
324 mpz_fdiv_q_2exp(remaining.value, remaining.value, 1);
325 if (mpz_sgn(remaining.value) != 0) base = base * base;
326 }
327 return result;
328}
329} // namespace detail
330
334template <class X, class Y>
335 requires((std::same_as<X, exact_rational> || std::same_as<X, decimal_literal>) &&
336 (std::same_as<Y, exact_rational> || std::same_as<Y, decimal_literal>))
337inline exact_rational pow(X const& base, Y const& power)
338{
339 exact_rational const x(base), y(power);
340 (void)x.native_handle();
341 auto exponent = y.native_handle();
342 auto degree = mpq_denref(exponent);
343 if (mpz_cmp_ui(degree, 1) == 0) return detail::integer_power(x, mpq_numref(exponent));
344 if (x == 0 || x == 1 || (x == -1 && mpz_odd_p(degree))) return detail::integer_power(x, mpq_numref(exponent));
345 // A nontrivial integer nth power needs at least n+1 bits. An unrepresentably
346 // large degree cannot have a rational root in an in-memory GMP operand.
347 if (mpz_fits_ulong_p(degree))
348 if (auto root = rational_root(x, mpz_get_ui(degree))) return detail::integer_power(*root, mpq_numref(exponent));
349 throw std::logic_error("exact_rational: power requires finite working precision");
350}
351
353template <class X>
354 requires(std::same_as<X, exact_rational> || std::same_as<X, decimal_literal>)
355inline exact_rational sqrt(X const& value)
356{
357 exact_rational const x(value);
358 if (auto root = rational_root(x, 2)) return *root;
359 throw std::logic_error("exact_rational: square root requires finite working precision");
360}
361
363template <class T>
365 (std::integral<std::remove_cvref_t<T>> && !std::same_as<std::remove_cvref_t<T>, bool>) ||
366 std::same_as<std::remove_cvref_t<T>, exact_rational> || std::same_as<std::remove_cvref_t<T>, decimal_literal>;
367
368namespace literals
369{
372constexpr decimal_literal operator""_mp(char const* text) { return decimal_literal(text); }
373} // namespace literals
374
375} // namespace uni20
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
A precisionless decimal spelling produced by the _mp literal operator.
Definition exact_rational.hpp:27
constexpr std::string_view spelling() const noexcept
Definition exact_rational.hpp:32
mpreal at(Precision precision) const
Materialize the literal with one rounding to the requested precision.
Definition mpreal.hpp:554
constexpr bool is_exact() const noexcept
Definition exact_rational.hpp:31
Owning arbitrary-size rational with exact, eager arithmetic.
Definition exact_rational.hpp:161
void swap(exact_rational &other) noexcept
Exchange payload ownership, including unset states, without allocating.
Definition exact_rational.hpp:206
std::string to_string() const
Return the canonical numerator/denominator, or an integer when exact.
Definition exact_rational.hpp:225
constexpr bool initialized() const noexcept
Whether this object owns a rational value rather than being unset.
Definition exact_rational.hpp:169
friend exact_rational operator/(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:265
exact_rational(exact_rational &&other) noexcept
Transfer the payload without allocating, leaving the source unset.
Definition exact_rational.hpp:195
exact_rational & operator=(exact_rational other) noexcept
Definition exact_rational.hpp:196
exact_rational(std::string_view text)
Definition exact_rational.hpp:172
friend exact_rational operator-(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:253
exact_rational()
Definition exact_rational.hpp:163
constexpr bool is_exact() const noexcept
Whether this object carries an exact value; false when unset.
Definition exact_rational.hpp:171
exact_rational(exact_rational const &other)
Definition exact_rational.hpp:185
friend exact_rational operator*(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:259
~exact_rational()
Definition exact_rational.hpp:201
exact_rational(I value)
Definition exact_rational.hpp:177
mpreal at(Precision precision) const
Convert to a real value with one rounding at explicit precision.
Definition mpreal.hpp:553
friend exact_rational operator+(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:247
mpq_srcptr native_handle() const
Borrow the read-only GMP rational while this object retains its payload.
Definition exact_rational.hpp:218
friend std::optional< exact_rational > rational_root(exact_rational const &, unsigned long)
Return a rational nth root when it exists, without choosing a precision.
Definition exact_rational.hpp:296
exact_rational(decimal_literal literal)
Definition exact_rational.hpp:173
Owning exact rational or finite-precision MPFR real, with eager arithmetic.
Definition mpreal.hpp:74
bool empty() const noexcept
Return whether this document has no spans.
Definition presentation.cpp:1182
Exact numeric sources accepted without an approximation precision.
Definition exact_rational.hpp:364
locale_t value
Definition data_table.cpp:49
presentation::styled_text text
Definition display.cpp:141
Definition cli.cpp:6
Scalar dot(std::span< Scalar const > x, std::span< Scalar const > y)
Compute a BLAS-style real dot product.
Definition dense_linalg.hpp:240
Definition async/async.hpp:26
constexpr auto operator*(basic_half_int< T > lhs, U rhs) -> basic_half_int< T >
Multiply a half-integer value by an integral scalar.
Definition half_int.hpp:367
bool operator==(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:284
exact_rational sqrt(X const &value)
Return the nonnegative rational square root, or require finite precision.
Definition exact_rational.hpp:355
auto degree(QNum const &q) -> int
Return the integral degree of a quantum number.
Definition qnum.hpp:270
constexpr auto operator-(basic_half_int< T > lhs, basic_half_int< T > rhs) -> basic_half_int< T >
Subtract two half-integer values.
Definition half_int.hpp:345
std::optional< exact_rational > rational_root(exact_rational const &x, unsigned long degree)
Return a rational nth root when it exists, without choosing a precision.
Definition exact_rational.hpp:296
exact_rational pow(X const &base, Y const &power)
Evaluate a rational power exactly when its real result is rational.
Definition exact_rational.hpp:337
void scale(Tensor &tensor, Scalar factor)
Multiply every stored numerical block by a scalar in place.
Definition block_tensor_linear.hpp:400
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
std::strong_ordering operator<=>(exact_rational const &a, exact_rational const &b)
Definition exact_rational.hpp:288