Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
scalar_io.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "numeric_limits.hpp"
4#include "scalar_concepts.hpp"
5#include "types.hpp"
6#if UNI20_HAS_MPREAL
7#include "mpreal.hpp"
8#endif
9#if UNI20_HAS_MPREAL
10#include "mpcomplex.hpp"
11#endif
12
13#include <cerrno>
14#include <cmath>
15#include <cstdlib>
16#include <fmt/format.h>
17#include <istream>
18#include <stdexcept>
19#include <string>
20#include <string_view>
21#include <type_traits>
22
23namespace uni20
24{
25
29{
30 general,
31 fixed,
33};
34
46
47namespace detail
48{
49#if UNI20_HAS_MPREAL
50// Decimal rounding of an exact rational must not pass through a binary float:
51// even an arbitrarily wide temporary can move a decimal halfway case off its tie.
52inline std::string format_exact_decimal(exact_rational const& value, scalar_format_options const& options)
53{
54 auto q = value.native_handle();
55 int const requested = options.precision;
56 if (requested < 0)
57 {
58 if (requested == -1 && mpz_cmp_ui(mpq_denref(q), 1) == 0) return value.to_string();
59 throw std::invalid_argument("exact decimal output requires an explicit digit count or fraction formatting");
60 }
61 if (requested == 0 && options.notation == real_format_notation::general)
62 throw std::invalid_argument("general decimal output requires a positive digit count");
63
64 mp_integer numerator, denominator, power, rounded, remainder;
65 mpz_abs(numerator.value, mpq_numref(q));
66 mpz_set(denominator.value, mpq_denref(q));
67 bool const negative = mpq_sgn(q) < 0;
68 long exponent = 0;
69 bool const fixed = options.notation == real_format_notation::fixed;
70 bool const general = options.notation == real_format_notation::general;
71 if (!fixed && mpz_sgn(numerator.value) != 0)
72 {
73 // Decimal digit estimates can be one too large. Correct them by exact comparisons.
74 exponent = static_cast<long>(mpz_sizeinbase(numerator.value, 10)) -
75 static_cast<long>(mpz_sizeinbase(denominator.value, 10));
76 auto compare_power = [&](long e) {
77 mpz_ui_pow_ui(power.value, 10, static_cast<unsigned long>(e < 0 ? -e : e));
78 if (e >= 0)
79 {
80 mpz_mul(remainder.value, denominator.value, power.value);
81 return mpz_cmp(numerator.value, remainder.value);
82 }
83 mpz_mul(remainder.value, numerator.value, power.value);
84 return mpz_cmp(remainder.value, denominator.value);
85 };
86 while (compare_power(exponent) < 0)
87 --exponent;
88 while (compare_power(exponent + 1) >= 0)
89 ++exponent;
90 }
91 long const fractional = general ? static_cast<long>(requested) - 1 : requested;
92 long const scale = fixed ? requested : fractional - exponent;
93 mpz_ui_pow_ui(power.value, 10, static_cast<unsigned long>(scale < 0 ? -scale : scale));
94 if (scale >= 0)
95 mpz_mul(numerator.value, numerator.value, power.value);
96 else
97 mpz_mul(denominator.value, denominator.value, power.value);
98 mpz_tdiv_qr(rounded.value, remainder.value, numerator.value, denominator.value);
99 mpz_mul_2exp(remainder.value, remainder.value, 1);
100 int const halfway = mpz_cmp(remainder.value, denominator.value);
101 if (halfway > 0 || (halfway == 0 && mpz_odd_p(rounded.value))) mpz_add_ui(rounded.value, rounded.value, 1);
102 std::string digits(mpz_sizeinbase(rounded.value, 10) + 2, '\0');
103 mpz_get_str(digits.data(), 10, rounded.value);
104 digits.resize(std::char_traits<char>::length(digits.c_str()));
105 if (!fixed && digits.size() > static_cast<std::size_t>(fractional + 1))
106 {
107 digits.pop_back(); // A rounding carry, e.g. 9.995 -> 1.000e+01.
108 ++exponent;
109 }
110 bool const scientific = !fixed && (!general || exponent < -4 || exponent >= requested);
111 long const decimal_places = fixed ? requested : (scientific ? fractional : fractional - exponent);
112 if (decimal_places > 0)
113 {
114 auto places = static_cast<std::size_t>(decimal_places);
115 if (digits.size() <= places) digits.insert(0, places + 1 - digits.size(), '0');
116 digits.insert(digits.size() - places, 1, '.');
117 if (general)
118 {
119 while (digits.back() == '0')
120 digits.pop_back();
121 if (digits.back() == '.') digits.pop_back();
122 }
123 }
124 if (scientific) digits += fmt::format("e{}{:02}", exponent < 0 ? '-' : '+', exponent < 0 ? -exponent : exponent);
125 if (negative) digits.insert(0, 1, '-');
126 return digits;
127}
128
129inline std::string format_mpreal(mpreal const& value, scalar_format_options const& options)
130{
131 if (value.is_exact())
132 return options.exact_fractions ? value.to_string() : format_exact_decimal(value.exact_value(), options);
133 auto const requested = options.precision < 0 ? mpfr_get_str_ndigits(10, value.precision().bit_count())
134 : static_cast<std::size_t>(options.precision);
135 if (requested > static_cast<std::size_t>(std::numeric_limits<int>::max()))
136 throw std::length_error("mpreal: decimal formatting precision is too large");
137 char const* format = options.notation == real_format_notation::fixed ? "%.*RNf"
138 : options.notation == real_format_notation::scientific ? "%.*RNe"
139 : "%.*RNg";
140 char* buffer = nullptr;
141 int const count = mpfr_asprintf(&buffer, format, static_cast<int>(requested), value.native_handle());
142 std::unique_ptr<char, decltype(&mpfr_free_str)> owned(buffer, &mpfr_free_str);
143 if (count < 0) throw std::runtime_error("mpreal: decimal formatting failed");
144 return std::string(buffer, static_cast<std::size_t>(count));
145}
146#endif
147
148template <typename T> [[nodiscard]] int effective_precision(scalar_format_options const& options)
149{
150 if (options.precision >= 0)
151 {
152 return options.precision;
153 }
154 return uni20::numeric_limits<std::remove_cv_t<T>>::max_digits10();
155}
156
157[[nodiscard]] inline char printf_notation(real_format_notation notation)
158{
159 switch (notation)
160 {
162 return 'f';
164 return 'e';
166 return 'g';
167 }
168 return 'g';
169}
170
171#if UNI20_HAS_MPREAL
172inline bool scalar_signbit(mpreal const& value) { return uni20::signbit(value); }
173#endif
174
175template <typename T> [[nodiscard]] bool scalar_signbit(T value)
176{
177 return std::signbit(static_cast<long double>(value));
178}
179
180template <typename T> [[nodiscard]] std::string fmt_real(T value, scalar_format_options const& options)
181{
182 int const precision = effective_precision<T>(options);
183 switch (options.notation)
184 {
186 return fmt::format("{:.{}f}", value, precision);
188 return fmt::format("{:.{}e}", value, precision);
190 return fmt::format("{:.{}g}", value, precision);
191 }
192 return fmt::format("{}", value);
193}
194
195#if UNI20_HAS_FLOAT128 && UNI20_FLOAT128_PROVIDER_MPLAPACK
196[[nodiscard]] inline std::string format_float128(uni20::float128 value, scalar_format_options const& options)
197{
198#if defined(MPLAPACK_BINARY128_MODE) && (MPLAPACK_BINARY128_MODE == MPLAPACK_BINARY128_MODE_FLOAT128)
199 int const precision = effective_precision<uni20::float128>(options);
200 char const notation = printf_notation(options.notation);
201 std::string const format = fmt::format("%.{}{}", precision, notation);
202
203 char fixed_buffer[128] = {};
204 int const fixed_count = ::strfromf128(fixed_buffer, sizeof(fixed_buffer), format.c_str(), value);
205 if (fixed_count < 0)
206 {
207 throw std::runtime_error("failed to format uni20::float128 value");
208 }
209 if (static_cast<std::size_t>(fixed_count) < sizeof(fixed_buffer))
210 {
211 return std::string(fixed_buffer, static_cast<std::size_t>(fixed_count));
212 }
213
214 std::string dynamic_buffer(static_cast<std::size_t>(fixed_count) + 1, '\0');
215 int const dynamic_count = ::strfromf128(dynamic_buffer.data(), dynamic_buffer.size(), format.c_str(), value);
216 if (dynamic_count < 0)
217 {
218 throw std::runtime_error("failed to format uni20::float128 value");
219 }
220 dynamic_buffer.resize(static_cast<std::size_t>(dynamic_count));
221 return dynamic_buffer;
222#else
223 // This diagnostic fallback does not require binary128 numeric_limits, which
224 // older standard libraries may omit. It is not admitted by DataTableValue.
225 (void)options;
226 return fmt::format("{}", static_cast<long double>(value));
227#endif
228}
229
230[[nodiscard]] inline uni20::float128 parse_float128(std::string_view text)
231{
232 std::string buffer(text);
233 char* end = nullptr;
234 errno = 0;
235#if defined(MPLAPACK_BINARY128_MODE) && (MPLAPACK_BINARY128_MODE == MPLAPACK_BINARY128_MODE_FLOAT128)
236 uni20::float128 const parsed = ::strtof128(buffer.c_str(), &end);
237#else
238 long double const parsed = std::strtold(buffer.c_str(), &end);
239#endif
240 if (end == buffer.c_str() || *end != '\0')
241 {
242 throw std::invalid_argument(fmt::format("invalid float128 value: {}", text));
243 }
244 if (errno == ERANGE)
245 {
246 throw std::out_of_range(fmt::format("float128 value out of range: {}", text));
247 }
248 return static_cast<uni20::float128>(parsed);
249}
250#endif
251
252template <typename T> [[nodiscard]] T parse_builtin_real(std::string_view text)
253{
254 std::string buffer(text);
255 char* end = nullptr;
256 errno = 0;
257
258 if constexpr (std::same_as<T, float>)
259 {
260 float const parsed = std::strtof(buffer.c_str(), &end);
261 if (end == buffer.c_str() || *end != '\0')
262 {
263 throw std::invalid_argument(fmt::format("invalid real value: {}", text));
264 }
265 if (errno == ERANGE)
266 {
267 throw std::out_of_range(fmt::format("real value out of range: {}", text));
268 }
269 return parsed;
270 }
271 else if constexpr (std::same_as<T, double>)
272 {
273 double const parsed = std::strtod(buffer.c_str(), &end);
274 if (end == buffer.c_str() || *end != '\0')
275 {
276 throw std::invalid_argument(fmt::format("invalid real value: {}", text));
277 }
278 if (errno == ERANGE)
279 {
280 throw std::out_of_range(fmt::format("real value out of range: {}", text));
281 }
282 return parsed;
283 }
284 else
285 {
286 long double const parsed = std::strtold(buffer.c_str(), &end);
287 if (end == buffer.c_str() || *end != '\0')
288 {
289 throw std::invalid_argument(fmt::format("invalid real value: {}", text));
290 }
291 if (errno == ERANGE)
292 {
293 throw std::out_of_range(fmt::format("real value out of range: {}", text));
294 }
295 return static_cast<T>(parsed);
296 }
297}
298} // namespace detail
299
306template <Real T> [[nodiscard]] std::string format_real(T value, scalar_format_options const& options = {})
307{
308 using real_type = std::remove_cv_t<T>;
309#if UNI20_HAS_MPREAL
310 if constexpr (std::same_as<real_type, mpreal>)
311 {
312 if (options.normalize_negative_zero && value == 0) value = mpreal(value.precision());
313 return detail::format_mpreal(value, options);
314 }
315 else
316#endif
317 {
318 if (options.normalize_negative_zero && value == real_type{})
319 {
320 value = real_type{};
321 }
322
323#if UNI20_HAS_FLOAT128
324 if constexpr (std::same_as<real_type, uni20::float128>)
325 {
326 return detail::format_float128(value, options);
327 }
328 else
329#endif
330 {
331 return detail::fmt_real(value, options);
332 }
333 }
334}
335
342template <Complex T> [[nodiscard]] std::string format_complex(T const& value, scalar_format_options const& options = {})
343{
344 using real_type = make_real_t<T>;
345 real_type imag = value.imag();
346 bool const negative_imag = detail::scalar_signbit(imag) && !(options.normalize_negative_zero && imag == 0);
347 if (negative_imag)
348 {
349 imag = -imag;
350 }
351 else if (options.normalize_negative_zero && imag == 0 && detail::scalar_signbit(imag))
352 {
353 imag = -imag;
354 }
355
356 return format_real(value.real(), options) + (negative_imag ? "-" : "+") + format_real(imag, options) +
357 std::string(options.imaginary_unit);
358}
359
366template <RealOrComplex T>
367[[nodiscard]] std::string format_scalar(T const& value, scalar_format_options const& options = {})
368{
369 if constexpr (Real<T>)
370 {
371 return format_real(value, options);
372 }
373 else
374 {
375 return format_complex(value, options);
376 }
377}
378
386template <Real T>
387#if UNI20_HAS_MPREAL
388 requires(!std::same_as<std::remove_cv_t<T>, mpreal>)
389#endif
390[[nodiscard]] T parse_real(std::string_view text)
391{
392 using real_type = std::remove_cv_t<T>;
393#if UNI20_HAS_FLOAT128
394 if constexpr (std::same_as<real_type, uni20::float128>)
395 {
396 return detail::parse_float128(text);
397 }
398 else
399#endif
400 {
401 return detail::parse_builtin_real<real_type>(text);
402 }
403}
404
405#if UNI20_HAS_MPREAL
407template <Real T>
408 requires std::same_as<std::remove_cv_t<T>, mpreal>
409[[nodiscard]] T parse_real(std::string_view text, Precision precision)
410{
411 return mpreal(text, precision);
412}
413#endif
414
425template <Real T> std::istream& read_real(std::istream& stream, T& value)
426{
427 std::string token;
428 stream >> token;
429 if (!stream)
430 {
431 return stream;
432 }
433
434 try
435 {
436#if UNI20_HAS_MPREAL
437 if constexpr (std::same_as<std::remove_cv_t<T>, mpreal>)
438 value = parse_real<T>(token, value.precision());
439 else
440#endif
441 value = parse_real<T>(token);
442 }
443 catch (...)
444 {
445 stream.setstate(std::ios::failbit);
446 }
447 return stream;
448}
449
450} // namespace uni20
451
452#if UNI20_HAS_FLOAT128
453template <> struct fmt::formatter<uni20::float128>
454{
455 constexpr auto parse(format_parse_context& ctx)
456 {
457 auto it = ctx.begin();
458 if (it != ctx.end() && *it != '}')
459 {
460 throw fmt::format_error("uni20::float128 formatter currently supports only default '{}'");
461 }
462 return it;
463 }
464
465 template <typename FormatContext> auto format(uni20::float128 value, FormatContext& ctx) const
466 {
467 return fmt::format_to(ctx.out(), "{}", uni20::format_real(value));
468 }
469};
470
471template <> struct fmt::formatter<uni20::complex<uni20::float128>>
472{
473 constexpr auto parse(format_parse_context& ctx)
474 {
475 auto it = ctx.begin();
476 if (it != ctx.end() && *it != '}')
477 {
478 throw fmt::format_error("uni20::complex<uni20::float128> formatter currently supports only default '{}'");
479 }
480 return it;
481 }
482
483 template <typename FormatContext>
484 auto format(uni20::complex<uni20::float128> const& value, FormatContext& ctx) const
485 {
486 return fmt::format_to(ctx.out(), "{}", uni20::format_complex(value));
487 }
488};
489#endif
Owning arbitrary-size rational with exact, eager arithmetic.
Definition exact_rational.hpp:161
Owning exact rational or finite-precision MPFR real, with eager arithmetic.
Definition mpreal.hpp:74
locale_t value
Definition data_table.cpp:49
presentation::styled_text text
Definition display.cpp:141
std::istream & read_real(std::istream &stream, T &value)
Read a Uni20 real scalar from a stream token.
Definition scalar_io.hpp:425
std::string format_complex(T const &value, scalar_format_options const &options={})
Format a Uni20 complex scalar as real+imagi.
Definition scalar_io.hpp:342
T parse_real(std::string_view text)
Parse a Uni20 real scalar from text.
Definition scalar_io.hpp:390
typename detail::complex_type< Real >::type complex
Project-level complex scalar spelling.
Definition types.hpp:66
std::string format_real(T value, scalar_format_options const &options={})
Format a Uni20 real scalar.
Definition scalar_io.hpp:306
real_format_notation
Selects how real scalar values are rendered as text.
Definition scalar_io.hpp:29
std::string format_scalar(T const &value, scalar_format_options const &options={})
Format any Uni20 real or complex scalar.
Definition scalar_io.hpp:367
Definition qnum.hpp:403
parse_result parse(CLI::App &app, int argc, char const *const *argv, parse_policy policy)
Parse argc/argv, preserving CLI11's argument boundaries and help precedence.
Definition cli.cpp:116
constexpr detail::scalar_math::imag_fn imag
Read the imaginary component by value; real zero retains the input's type and precision.
Definition math.hpp:673
Definition async/async.hpp:26
bool signbit(mpreal const &x)
Definition mpreal.hpp:642
void scale(Tensor &tensor, Scalar factor)
Multiply every stored numerical block by a scalar in place.
Definition block_tensor_linear.hpp:400
mpreal remainder(mpreal const &a, mpreal const &b)
Nearest-even-quotient remainder; exact operands retain exact rational results.
Definition mpreal_functions.hpp:534
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16
Formatting options for Uni20 real and complex scalars.
Definition scalar_io.hpp:38
real_format_notation notation
Definition scalar_io.hpp:40
int precision
Definition scalar_io.hpp:39
bool normalize_negative_zero
Definition scalar_io.hpp:41
bool exact_fractions
Exact rationals use fractions unless decimal rounding is explicitly requested.
Definition scalar_io.hpp:44
std::string_view imaginary_unit
Definition scalar_io.hpp:42