quanta 0.1.0
Type-safe units for temperature, frequency, electrical and spatial quantities, modeled after std::chrono
Loading...
Searching...
No Matches
core.hpp
Go to the documentation of this file.
1#pragma once
2
10#include <charconv>
11#include <concepts>
12#include <cstddef>
13#include <cstdint>
14#include <cstdlib>
15#include <limits>
16#include <ratio>
17#include <string>
18#include <system_error>
19#include <type_traits>
20#include <utility>
21#include <version> // pulls in __cpp_lib_format so the detection below works
22
32#ifndef CONFIG_QUANTA_STD_FORMAT
33#if __has_include(<format>) && defined(__cpp_lib_format)
34#define CONFIG_QUANTA_STD_FORMAT 1
35#else
36#define CONFIG_QUANTA_STD_FORMAT 0
37#endif
38#endif
39
40// The per-domain macros of the pre-quanta libraries are gone. Only a value of 0
41// (or an empty definition) is diagnosed: under ESP-IDF a stale component left
42// installed defines them to 1 through sdkconfig.h, which is harmless.
43#if defined(CONFIG_THERMO_STD_FORMAT) && (CONFIG_THERMO_STD_FORMAT + 0) == 0
44#error "CONFIG_THERMO_STD_FORMAT has been replaced by CONFIG_QUANTA_STD_FORMAT"
45#endif
46#if defined(CONFIG_FREQUENCY_STD_FORMAT) && (CONFIG_FREQUENCY_STD_FORMAT + 0) == 0
47#error "CONFIG_FREQUENCY_STD_FORMAT has been replaced by CONFIG_QUANTA_STD_FORMAT"
48#endif
49#if defined(CONFIG_ELECTRO_STD_FORMAT) && (CONFIG_ELECTRO_STD_FORMAT + 0) == 0
50#error "CONFIG_ELECTRO_STD_FORMAT has been replaced by CONFIG_QUANTA_STD_FORMAT"
51#endif
52#if defined(CONFIG_SPATIAL_STD_FORMAT) && (CONFIG_SPATIAL_STD_FORMAT + 0) == 0
53#error "CONFIG_SPATIAL_STD_FORMAT has been replaced by CONFIG_QUANTA_STD_FORMAT"
54#endif
55
56#if CONFIG_QUANTA_STD_FORMAT
57#include <format>
58#endif
59
73#if !defined(__GNUC__) || defined(__clang__) || __GNUC__ >= 14
74#define QUANTA_HAS_UTF8_LITERALS 1
75#else
76#define QUANTA_HAS_UTF8_LITERALS 0
77#endif
78
90namespace quanta {
91
115template<typename T>
116struct treat_as_inexact : std::bool_constant<std::floating_point<T>> {};
117
118template<typename T>
120
125 explicit base_unit_t() = default;
126};
127
140inline constexpr base_unit_t base_unit{};
141
145struct scaled_t {
146 explicit scaled_t() = default;
147};
148
161inline constexpr scaled_t scaled{};
162
164template<typename T, template<typename...> class Template>
165struct _is_specialization_of : std::false_type {};
166
167template<template<typename...> class Template, typename... Args>
168struct _is_specialization_of<Template<Args...>, Template> : std::true_type {};
169
170template<typename T, template<typename...> class Template>
171inline constexpr bool _is_specialization_of_v = _is_specialization_of<T, Template>::value;
172
173template<typename T>
174struct _is_ratio : std::false_type {};
175
176template<std::intmax_t Num, std::intmax_t Denom>
177struct _is_ratio<std::ratio<Num, Denom>> : std::true_type {};
178
179consteval intmax_t _gcd(intmax_t m, intmax_t n) noexcept {
180 while (n != 0) {
181 intmax_t rem = m % n;
182 m = n;
183 n = rem;
184 }
185 return m;
186}
187
188// Runtime GCD for integer types (using Euclidean algorithm)
189template<typename T>
190constexpr T _runtime_gcd(T m, T n) noexcept {
191 if (m < 0) {
192 m = -m;
193 }
194 if (n < 0) {
195 n = -n;
196 }
197 while (n != 0) {
198 T rem = m % n;
199 m = n;
200 n = rem;
201 }
202 return m;
203}
204
205// R1 / R2 as a fraction in lowest terms, computed as std::ratio_divide does
206// but reporting overflow of intmax_t (fits is false) rather than failing to
207// compile, so that it can be used in constraints.
208struct _ratio_quotient {
209 intmax_t num;
210 intmax_t den;
211 bool fits;
212};
213
214// Whether a * b fits in intmax_t, for non-negative a and b.
215constexpr bool _product_fits(intmax_t a, intmax_t b) {
216 return a >= 0 && b >= 0 && (a == 0 || b <= std::numeric_limits<intmax_t>::max() / a);
217}
218
219// |n|, for any n but the most negative (which no std::ratio holds).
220constexpr intmax_t _abs_ratio_term(intmax_t n) {
221 return n < 0 ? -n : n;
222}
223
224template<typename R1, typename R2>
225inline constexpr _ratio_quotient _safe_ratio_divide = [] {
226 if constexpr (R2::num == 0) {
227 return _ratio_quotient{0, 1, false};
228 } else {
229 // As std::ratio_divide: cancel the common factors of the numerators and
230 // of the denominators, then multiply crosswise.
231 constexpr intmax_t g1 = _gcd(_abs_ratio_term(R1::num), _abs_ratio_term(R2::num));
232 constexpr intmax_t g2 = _gcd(R1::den, R2::den);
233 constexpr intmax_t a = R1::num / g1;
234 constexpr intmax_t b = R2::den / g2;
235 constexpr intmax_t c = R1::den / g2;
236 constexpr intmax_t d = R2::num / g1;
237 if constexpr (_product_fits(_abs_ratio_term(a), b) && _product_fits(c, _abs_ratio_term(d))) {
238 constexpr intmax_t num = d < 0 ? -(a * b) : a * b;
239 constexpr intmax_t den = c * _abs_ratio_term(d);
240 constexpr intmax_t g = _gcd(_abs_ratio_term(num), den);
241 return _ratio_quotient{num / g, den / g, true};
242 } else {
243 return _ratio_quotient{0, 1, false};
244 }
245 }
246}();
247
248// Whether a - b fits in intmax_t (excluding its most negative value, as
249// std::ratio does).
250constexpr bool _difference_fits(intmax_t a, intmax_t b) {
251 constexpr intmax_t max = std::numeric_limits<intmax_t>::max();
252 return b >= 0 ? a >= -max + b : a <= max + b;
253}
254
255// a / b as a reduced fraction (b != 0), the reduction std::ratio performs.
256consteval _ratio_quotient _reduced_quotient(intmax_t a, intmax_t b) {
257 const intmax_t g = _gcd(_abs_ratio_term(a), _abs_ratio_term(b));
258 const intmax_t sign = (a < 0) != (b < 0) ? -1 : 1;
259 return {sign * (_abs_ratio_term(a) / g), _abs_ratio_term(b) / g, true};
260}
261
262// The gcd of |m| and |n|, or 1 if both are 0: the factor std::ratio's
263// arithmetic cancels, which leaves a zero term as it is.
264consteval intmax_t _cancelling_gcd(intmax_t m, intmax_t n) {
265 const intmax_t g = _gcd(_abs_ratio_term(m), _abs_ratio_term(n));
266 return g == 0 ? 1 : g;
267}
268
269// R1 - R2 as a fraction in lowest terms, computed step by step as
270// std::ratio_subtract does (cancelling common factors, then a product, a
271// difference and a ratio_multiply), but reporting overflow of intmax_t (fits
272// is false) rather than failing to compile.
273template<typename R1, typename R2>
274inline constexpr _ratio_quotient _safe_ratio_subtract = [] {
275 constexpr intmax_t gn = _cancelling_gcd(R1::num, R2::num);
276 constexpr intmax_t gd = _gcd(R1::den, R2::den);
277 constexpr intmax_t x1 = R1::num / gn;
278 constexpr intmax_t y1 = R2::den / gd;
279 constexpr intmax_t x2 = R2::num / gn;
280 constexpr intmax_t y2 = R1::den / gd;
281 if constexpr (!_product_fits(_abs_ratio_term(x1), y1) || !_product_fits(_abs_ratio_term(x2), y2)) {
282 return _ratio_quotient{0, 1, false};
283 } else if constexpr (!_difference_fits(x1 * y1, x2 * y2)) {
284 return _ratio_quotient{0, 1, false};
285 } else {
286 // ratio<gn, R1::den / gd> times ratio<x1 * y1 - x2 * y2, R2::den>
287 constexpr _ratio_quotient a = _reduced_quotient(gn, R1::den / gd);
288 constexpr _ratio_quotient b = _reduced_quotient(x1 * y1 - x2 * y2, R2::den);
289 constexpr intmax_t g1 = _cancelling_gcd(a.num, b.den);
290 constexpr intmax_t g2 = _cancelling_gcd(a.den, b.num);
291 constexpr intmax_t n1 = a.num / g1;
292 constexpr intmax_t n2 = b.num / g2;
293 constexpr intmax_t d1 = b.den / g1;
294 constexpr intmax_t d2 = a.den / g2;
295 if constexpr (!_product_fits(_abs_ratio_term(n1), _abs_ratio_term(n2)) || !_product_fits(d1, d2)) {
296 return _ratio_quotient{0, 1, false};
297 } else {
298 return _reduced_quotient(n1 * n2, d1 * d2);
299 }
300 }
301}();
302
303// From / To is a whole number (that fits in intmax_t), so converting a count
304// from precision From to precision To loses nothing.
305template<typename From, typename To>
306concept _harmonic_precision = _safe_ratio_divide<From, To>.fits && _safe_ratio_divide<From, To>.den == 1;
307
308// Whether a conversion between two quantities of the same kind (whose reps
309// are convertible) loses nothing, and so is implicit: to a floating-point
310// rep, or to a precision that evenly divides the source's from a rep that is
311// not floating-point. Every other such conversion is explicit.
312namespace detail {
313template<typename FromRep, typename FromPrecision, typename ToRep, typename ToPrecision>
314concept _lossless_conversion =
316} // namespace detail
317
318consteval bool _is_power_of_ten(intmax_t n) {
319 while (n >= 10 && n % 10 == 0) {
320 n /= 10;
321 }
322 return n == 1;
323}
324
325consteval int _log10(intmax_t n) {
326 int e = 0;
327 while (n >= 10) {
328 n /= 10;
329 ++e;
330 }
331 return e;
332}
333
334template<typename Precision>
335inline constexpr int _precision_exponent = _log10(Precision::num) - _log10(Precision::den);
336
337// 10^k as a double, exact for |k| <= 22.
338constexpr double _pow10(int k) {
339 double r = 1.0;
340 for (int i = 0; i < (k < 0 ? -k : k); ++i) {
341 r *= 10.0;
342 }
343 return r;
344}
345
346// v x 10^k with a single rounding (exact power of ten for |k| <= 22).
347constexpr double _scale_pow10(double v, int k) {
348 return k >= 0 ? v * _pow10(k) : v / _pow10(-k);
349}
350
351// The shortest fixed-notation representation of v that round-trips: never
352// scientific notation, and no trailing zeros (2.5 -> "2.5", 1e6 -> "1000000",
353// 1e-15 -> "0.000000000000001"). Used wherever a floating-point value is
354// rendered without a format spec, so to_string and std::format agree.
355template<std::floating_point T>
356std::string _fixed_string(T v) {
357 std::string s(32, '\0');
358 for (;;) {
359 const auto result = std::to_chars(s.data(), s.data() + s.size(), v, std::chars_format::fixed);
360 if (result.ec == std::errc()) {
361 s.resize(static_cast<std::size_t>(result.ptr - s.data()));
362 return s;
363 }
364 s.resize(s.size() * 2);
365 }
366}
367
368// A count rendered without a format spec: integers exactly, floating-point
369// counts in shortest round-trip fixed notation.
370template<typename Rep>
371std::string _count_string(const Rep& r) {
372 if constexpr (std::floating_point<Rep>) {
373 return _fixed_string(r);
374 } else {
375 return std::to_string(r);
376 }
377}
378
379#if CONFIG_QUANTA_STD_FORMAT
380// Appends a NUL-terminated string to a format output iterator.
381template<typename OutputIt>
382constexpr OutputIt _format_append(OutputIt out, const char* s) {
383 for (; *s != '\0'; ++s) {
384 *out++ = *s;
385 }
386 return out;
387}
388
389// Reports an unusable format spec from a formatter's parse().
390//
391// std::format constant-evaluates parse() to check the format string, so
392// throwing there makes a bad spec a compile error rather than a runtime fault.
393// Where exceptions are unavailable, calling a non-constexpr function fails that
394// same constant evaluation and so reports the error at compile time too; a
395// runtime parse (std::vformat with a runtime format string) has no way to
396// report it and terminates.
397[[noreturn]] inline void _format_error(const char* what) {
398#if defined(__cpp_exceptions) && __cpp_exceptions
399 throw std::format_error(what);
400#else
401 (void)what;
402 std::abort();
403#endif
404}
405#endif
406
407// Internals shared by every domain: the parsing of integer literal characters,
408// and the conversion, rounding and display arithmetic of quantities.
409namespace detail {
410
411#ifdef __SIZEOF_INT128__
412// 128-bit integers for wide intermediates; __extension__ keeps -Wpedantic quiet.
414__extension__ typedef unsigned __int128 uint128_t;
415#endif
416
417// The integer type of the wide intermediates of an integral conversion whose
418// reps have the common type Common with intmax_t: a 128-bit integer where the
419// compiler has one, signed unless Common is unsigned, and Fallback elsewhere
420// (e.g. MSVC and 32-bit targets). The fallback is a policy of the caller:
421// Common itself keeps the intermediate as wide as the reps and of their
422// signedness, while intmax_t keeps it signed.
423template<typename Common, typename Fallback = Common>
424using _wide_integer =
425#ifdef __SIZEOF_INT128__
426 std::conditional_t<std::is_signed_v<Common>, int128_t, uint128_t>;
427#else
428 Fallback;
429#endif
430
431// The integer type of the wide intermediates of an integral conversion between
432// Rep1 and Rep2: unsigned when both reps are, signed otherwise, so that a
433// negative count of a signed rep stays negative whatever the other rep is.
434// 128-bit where the compiler has it, else uintmax_t or intmax_t.
435template<typename Rep1, typename Rep2>
436using _conversion_integer = std::conditional_t<
437 std::is_unsigned_v<Rep1> && std::is_unsigned_v<Rep2>,
440
441// The value of an integer literal's characters, following the C++ rules:
442// digit separators ('), hexadecimal (0x, 0X), binary (0b, 0B), octal (a
443// leading 0) or decimal. `valid` is false if a character is not a digit of the
444// literal's base (as in a floating-point literal such as 1.5), and `overflow`
445// is true if the value exceeds unsigned long long.
446struct parsed_literal {
447 unsigned long long value;
448 bool valid;
449 bool overflow;
450};
451
452template<char... Chars>
453consteval parsed_literal parse_literal() {
454 constexpr char chars[] = {Chars...};
455 constexpr std::size_t n = sizeof...(Chars);
456 unsigned base = 10;
457 std::size_t i = 0;
458 if (n > 2 && chars[0] == '0' && (chars[1] == 'x' || chars[1] == 'X')) {
459 base = 16;
460 i = 2;
461 } else if (n > 2 && chars[0] == '0' && (chars[1] == 'b' || chars[1] == 'B')) {
462 base = 2;
463 i = 2;
464 } else if (n > 1 && chars[0] == '0') {
465 base = 8;
466 i = 1;
467 }
468 parsed_literal result{0, true, false};
469 for (; i < n; ++i) {
470 const char c = chars[i];
471 unsigned digit;
472 if (c == '\'') {
473 continue;
474 } else if (c >= '0' && c <= '9') {
475 digit = static_cast<unsigned>(c - '0');
476 } else if (c >= 'a' && c <= 'f') {
477 digit = static_cast<unsigned>(c - 'a' + 10);
478 } else if (c >= 'A' && c <= 'F') {
479 digit = static_cast<unsigned>(c - 'A' + 10);
480 } else {
481 digit = base; // not a digit in any base
482 }
483 if (digit >= base) {
484 result.valid = false;
485 return result;
486 }
487 if (result.value > (std::numeric_limits<unsigned long long>::max() - digit) / base) {
488 result.overflow = true;
489 }
490 result.value = result.value * base + digit;
491 }
492 return result;
493}
494
495template<typename Quantity, char... Digits>
496constexpr Quantity check_overflow() {
497 constexpr parsed_literal parsed = parse_literal<Digits...>();
498 static_assert(parsed.valid, "invalid digit");
499 static_assert(!parsed.overflow, "literal value cannot be represented by unsigned long long");
500 constexpr typename Quantity::rep repval = parsed.value;
501 static_assert(
502 repval >= 0 && static_cast<unsigned long long>(repval) == parsed.value,
503 "literal value cannot be represented by the quantity type"
504 );
505 return Quantity(repval);
506}
507
508// count x Ratio as a double: the count, multiplied by the numerator and then
509// divided by the denominator, each step skipped when it is 1. Every domain's
510// base-unit form relies on this exact order of operations.
511template<typename Ratio, typename Rep>
512constexpr double _ratio_value(const Rep& count) {
513 double v = static_cast<double>(count);
514 if constexpr (Ratio::num != 1) {
515 v *= static_cast<double>(Ratio::num);
516 }
517 if constexpr (Ratio::den != 1) {
518 v /= static_cast<double>(Ratio::den);
519 }
520 return v;
521}
522
523// Precisions that are exactly 10^k of the base unit, for any integer k,
524// whether or not they have an SI prefix (std::deci and std::deca do not).
525template<typename Precision>
526concept _power_of_ten_precision = _is_power_of_ten(Precision::num) && _is_power_of_ten(Precision::den);
527
528// An entry of a scaled-form unit table: a unit 10^exponent times the base unit,
529// and its label (an SI prefix, or a whole unit suffix).
530struct _scale_entry {
531 int exponent;
532 const char* label;
533};
534
535// The SI prefixes from femto to tera, in ascending order.
536inline constexpr _scale_entry _si_scale[] = {
537 {-15, "f"},
538 {-12, "p"},
539 {-9, "n"},
540 {-6, "µ"},
541 {-3, "m"},
542 {0, ""},
543 {3, "k"},
544 {6, "M"},
545 {9, "G"},
546 {12, "T"},
547};
548
549// A value in the unit of a scale-table entry.
550struct _scaled {
551 double value;
552 const char* label;
553};
554
555// The value x x 10^exponent (of the base unit) in the largest unit of table
556// (ascending by exponent) in which its magnitude is at least 1, else in the
557// smallest. Each candidate is computed from x with a single scaling by a power
558// of ten, so values that land exactly on a unit are exactly 1 in it. Zero and
559// non-finite values are left to the caller.
560template<std::size_t N>
561_scaled _pick_scale(double x, int exponent, const _scale_entry (&table)[N]) {
562 for (std::size_t i = N; i-- > 1;) {
563 const double v = _scale_pow10(x, exponent - table[i].exponent);
564 if (v >= 1.0 || v <= -1.0) {
565 return {v, table[i].label};
566 }
567 }
568 return {_scale_pow10(x, exponent - table[0].exponent), table[0].label};
569}
570
571// The precision of the std::common_type of two quantities of the same kind:
572// the greatest ratio of which both precisions are whole multiples. The result
573// is spelled as std::ratio<gcd of numerators, lcm of denominators>, from the
574// reduced terms, and result types depend on that exact spelling.
575template<typename Precision1, typename Precision2>
576using _common_precision = std::ratio<
577 _gcd(Precision1::num, Precision2::num),
578 (Precision1::den / _gcd(Precision1::den, Precision2::den)) * Precision2::den>;
579
580// The operations _quantity_cast performs, outside its integral path, to
581// convert a FromRep count by the factor Factor (a std::ratio) to a ToRep
582// count, with Common the common type of ToRep, FromRep and intmax_t. As in
583// the cast, the count is a prvalue (count() returns it by value): converted
584// to ToRep directly for a factor of 1, and through Common otherwise.
585template<typename FromRep, typename ToRep, typename Factor, typename Common>
586concept _cast_steps =
587 (Factor::num == 1 && Factor::den == 1 && requires(FromRep (&count)()) { static_cast<ToRep>(count()); }) ||
588 (Factor::num != 1 && Factor::den == 1 && requires(FromRep (&count)()) {
589 static_cast<ToRep>(static_cast<Common>(count()) * static_cast<Common>(Factor::num));
590 }) || (Factor::num == 1 && Factor::den != 1 && requires(FromRep (&count)()) {
591 static_cast<ToRep>(static_cast<Common>(count()) / static_cast<Common>(Factor::den));
592 }) || (Factor::num != 1 && Factor::den != 1 && requires(FromRep (&count)()) {
593 static_cast<ToRep>(
594 static_cast<Common>(count()) * static_cast<Common>(Factor::num) / static_cast<Common>(Factor::den)
595 );
596 });
597
598template<typename FromRep, typename ToRep, typename Factor>
599concept _cast_through_common = requires { typename std::common_type<ToRep, FromRep, intmax_t>::type; } &&
601
602// The requirements of _quantity_cast, read off its code and checked by its
603// requires-clause: a plain copy to the same type; otherwise the conversion
604// factor must fit in a std::ratio (computed without naming
605// std::ratio_divide, whose overflow would be a hard error), and then integral
606// reps convert through wide integers, any others through their common type
607// with intmax_t. Only _quantity_cast and the public casts that call it
608// (whose diagnostics then name the requirement that failed) are constrained
609// on this: whatever else needs to know whether a conversion can be performed
610// asks whether the cast can be called (_quantity_castable), so that the
611// answer is the cast's own.
612template<typename From, typename To>
613concept _quantity_cast_requirements = std::is_same_v<To, From> ||
615 ((std::is_integral_v<typename From::rep> && std::is_integral_v<typename To::rep>) ||
617 typename From::rep,
618 typename To::rep,
619 std::ratio<
622
623// The conversion behind every domain's cast (delta_cast, frequency_cast,
624// quantity_cast): From and To are quantities of the same kind, each with a
625// count(), a rep and a precision, and To is constructible from a To::rep.
626// Integer-to-integer conversions use wider intermediate types (128-bit when
627// available) to minimize overflow risk during ratio arithmetic.
628template<typename To, typename From>
630constexpr To _quantity_cast(const From& q) {
631 if constexpr (std::is_same_v<To, From>) {
632 return q;
633 } else {
634 using rep = typename From::rep;
635 using to_rep = typename To::rep;
636 using cf = std::ratio_divide<typename From::precision, typename To::precision>;
637
638 // Use wider intermediate type for integer-to-integer conversions
639 if constexpr (std::is_integral_v<rep> && std::is_integral_v<to_rep>) {
641
642 if constexpr (cf::den == 1 && cf::num == 1) {
643 return To(static_cast<to_rep>(q.count()));
644 } else if constexpr (cf::den == 1) {
645 wider_t result = static_cast<wider_t>(q.count()) * static_cast<wider_t>(cf::num);
646 return To(static_cast<to_rep>(result));
647 } else if constexpr (cf::num == 1) {
648 wider_t result = static_cast<wider_t>(q.count()) / static_cast<wider_t>(cf::den);
649 return To(static_cast<to_rep>(result));
650 } else {
651 // Use GCD to reduce operands: count * num / den
652 // Compute g = gcd(count, den), then (count/g) * num / (den/g)
653 wider_t count = static_cast<wider_t>(q.count());
654 wider_t den = static_cast<wider_t>(cf::den);
655 wider_t g = _runtime_gcd(count, den);
656 wider_t reduced_count = count / g;
658 wider_t result = reduced_count * static_cast<wider_t>(cf::num) / reduced_den;
659 return To(static_cast<to_rep>(result));
660 }
661 } else {
662 // Floating-point path
663 using cr = std::common_type_t<to_rep, rep, intmax_t>;
664 if constexpr (cf::den == 1 && cf::num == 1) {
665 return To(static_cast<to_rep>(q.count()));
666 } else if constexpr (cf::den == 1) {
667 return To(static_cast<to_rep>(static_cast<cr>(q.count()) * static_cast<cr>(cf::num)));
668 } else if constexpr (cf::num == 1) {
669 return To(static_cast<to_rep>(static_cast<cr>(q.count()) / static_cast<cr>(cf::den)));
670 } else {
671 return To(
672 static_cast<to_rep>(
673 static_cast<cr>(q.count()) * static_cast<cr>(cf::num) / static_cast<cr>(cf::den)
674 )
675 );
676 }
677 }
678 }
679}
680
681// Whether _quantity_cast can convert a From to a To. The converting
682// constructors are constrained on this, so that a conversion is offered
683// exactly when the cast can perform it.
684template<typename From, typename To>
685concept _quantity_castable = requires(const From& q) { _quantity_cast<To>(q); };
686
687// The three-way comparison of a and b, two quantities of the same kind, as
688// negative, zero or positive. Integral reps are compared exactly, as counts of
689// their common precision in a wide integer, so that narrow reps cannot
690// overflow (the quantities' own comparison converts both to their common
691// type, whose rep is the narrower common type of the reps); any others as the
692// quantities compare. The wide integer is that of the conversions
693// (_conversion_integer): signed unless both reps are unsigned, so that a
694// negative count compares correctly with an unsigned one. When the cast
695// between the two precisions is possible, each scale fits in intmax_t, so a
696// 64-bit count times its scale fits in 128 bits; without a 128-bit integer,
697// the comparison is exact for reps narrower than 64 bits.
698template<typename Q1, typename Q2>
699constexpr int _compare(const Q1& a, const Q2& b) {
700 using rep1 = typename Q1::rep;
701 using rep2 = typename Q2::rep;
702
703 if constexpr (std::is_integral_v<rep1> && std::is_integral_v<rep2>) {
704 // Each precision is a whole multiple of the common one.
706 using scale1 = std::ratio_divide<typename Q1::precision, cp>;
707 using scale2 = std::ratio_divide<typename Q2::precision, cp>;
708 static_assert(scale1::den == 1 && scale2::den == 1);
710 const wide x = static_cast<wide>(a.count()) * static_cast<wide>(scale1::num);
711 const wide y = static_cast<wide>(b.count()) * static_cast<wide>(scale2::num);
712 return x < y ? -1 : x > y ? 1 : 0;
713 } else {
714 const auto c = a <=> b;
715 return c < 0 ? -1 : c > 0 ? 1 : 0;
716 }
717}
718
719// floor<To>(q), given cast, the domain's cast of q to To: the largest To not
720// greater than q. A To whose rep is not integral is not rounded.
721template<typename To, typename From>
722constexpr To _floor_cast(const To& cast, const From& q) {
723 using to_rep = typename To::rep;
724
725 if constexpr (std::is_integral_v<to_rep>) {
726 if (_compare(cast, q) > 0) {
727 return To(cast.count() - to_rep(1));
728 }
729 }
730
731 return cast;
732}
733
734// ceil<To>(q), given cast, the domain's cast of q to To: the smallest To not
735// less than q. A To whose rep is not integral is not rounded.
736template<typename To, typename From>
737constexpr To _ceil_cast(const To& cast, const From& q) {
738 using to_rep = typename To::rep;
739
740 if constexpr (std::is_integral_v<to_rep>) {
741 if (_compare(cast, q) < 0) {
742 return To(cast.count() + to_rep(1));
743 }
744 }
745
746 return cast;
747}
748
749// round<To>(q), given floor_of_q, which returns the domain's floor<To>(q),
750// and is called only where it is needed: the To nearest to q, ties to even. A
751// To whose rep is not integral is not rounded (floor is then a plain cast).
752template<typename To, typename From, typename Floor>
753constexpr To _round_cast(const From& q, Floor floor_of_q) {
754 using to_rep = typename To::rep;
755 using from_rep = typename From::rep;
756
757 if constexpr (std::is_integral_v<to_rep> && std::is_integral_v<from_rep>) {
758 // Round exactly in a wide integer, so that narrow reps cannot
759 // overflow; only the result converts to the target rep (floor's
760 // result, where q rounded down does not fit the target rep, has
761 // wrapped). In the common precision, of which each precision is a
762 // whole multiple, q is count * from_scale and a target tick is
763 // to_scale. Splitting count by to_scale first keeps the products
764 // within the result's magnitude where the operands are large.
766 using from_scale = std::ratio_divide<typename From::precision, cp>;
767 using to_scale = std::ratio_divide<typename To::precision, cp>;
768 static_assert(from_scale::den == 1 && to_scale::den == 1);
770 constexpr bool is_signed = !(std::is_unsigned_v<from_rep> && std::is_unsigned_v<to_rep>);
771 const wide scale = static_cast<wide>(from_scale::num);
772 const wide tick = static_cast<wide>(to_scale::num);
773
774 // count = whole * tick + part, with part in [0, tick)
775 const wide count = static_cast<wide>(q.count());
776 wide whole = count / tick;
777 wide part = count % tick;
778 if constexpr (is_signed) {
779 if (part < 0) {
780 whole -= 1;
781 part += tick;
782 }
783 }
784
785 // q rounded down, in target ticks, and what remains, in [0, tick)
786 const wide scaled_part = part * scale;
787 const wide down = whole * scale + scaled_part / tick;
788 const wide rem = scaled_part % tick;
789
790 // Nearer to down or to the tick above it; a tie goes to the even one.
791 const wide to_up = tick - rem;
792 const bool up = rem > to_up || (rem == to_up && down % 2 != 0);
793 return To(static_cast<to_rep>(up ? down + 1 : down));
794 } else if constexpr (std::is_integral_v<to_rep>) {
795 const To lower = floor_of_q();
796 const To upper = lower + To(to_rep(1));
797 const auto diff_lower = q - lower;
798 const auto diff_upper = upper - q;
799 if (diff_lower < diff_upper) {
800 return lower;
801 } else if (diff_lower > diff_upper) {
802 return upper;
803 } else {
804 // Tie: round to even
805 return (lower.count() % to_rep(2) == to_rep(0)) ? lower : upper;
806 }
807 } else {
808 return floor_of_q();
809 }
810}
811
812// The absolute value of a quantity, in its own type. Unary minus gives the
813// reduced precision, so it is converted back explicitly: a precision not in
814// lowest terms would otherwise make the conditional's operand types convert
815// both ways, and so be ambiguous.
816template<typename Q>
817constexpr Q _abs(const Q& q) {
818 return q >= Q::zero() ? q : Q(-q);
819}
820
821#if CONFIG_QUANTA_STD_FORMAT
822// The units a format spec can select with a leading marker letter.
823enum class _unit_form { own, power, base, scaled };
824
825// Consumes a leading marker letter of a format spec, if it is one of markers
826// ('p' power form, 'b' base unit, 's' scaled), and returns the unit form it
827// selects (own if there is none). A spec starting with a marker letter always
828// starts with the marker, so these letters cannot be the fill character of a
829// spec that starts with a fill.
830constexpr _unit_form _parse_unit_marker(std::format_parse_context& ctx, const char* markers) {
831 auto it = ctx.begin();
832 if (it == ctx.end()) {
833 return _unit_form::own;
834 }
835 for (const char* m = markers; *m != '\0'; ++m) {
836 if (*it == *m) {
837 ctx.advance_to(++it);
838 return *m == 'p' ? _unit_form::power : *m == 'b' ? _unit_form::base : _unit_form::scaled;
839 }
840 }
841 return _unit_form::own;
842}
843
844// The std::formatter implementation shared by the quantities of thermo,
845// frequency, electro and spatial; see each domain's formatter specializations
846// for the spec grammar. With no marker, "{}" renders the exact count and any
847// other spec the count as a double, followed by the type's own unit; a marker
848// selects another unit. Traits describes the formatted type:
849// - markers: the marker letters accepted ("bs", or "pbs" with the power form)
850// - has_own_unit: whether the type has a unit label of its own. If not, "{}"
851// is a format error, raised by no_own_unit_error() (which calls
852// _format_error with the domain's message, so that the message shows at
853// the point of the diagnostic), and any other spec without a marker
854// renders in the base unit.
855// - own_unit(power): the own unit (or its power form), obtained before
856// anything is written; append_own_unit(out, own_unit) appends it
857// - base_display(q), scaled_display(q): the value (as .value) and label in
858// the base unit or the scaled unit; append_label(out, display) appends the
859// label
860template<typename Traits>
861struct _unit_formatter {
862private:
863 std::formatter<double> _num;
864 bool _has_spec = false;
865 _unit_form _form = _unit_form::own;
866
867public:
868 constexpr auto parse(std::format_parse_context& ctx) {
869 _form = _parse_unit_marker(ctx, Traits::markers);
870 auto it = ctx.begin();
871 if (it == ctx.end() || *it == '}') {
872 if constexpr (!Traits::has_own_unit) {
873 if (_form == _unit_form::own) {
874 Traits::no_own_unit_error();
875 }
876 }
877 return it;
878 }
879 _has_spec = true;
880 return _num.parse(ctx);
881 }
882
883 template<typename Q, typename FormatContext>
884 auto format_quantity(const Q& q, FormatContext& ctx) const {
885 if constexpr (Traits::has_own_unit) {
886 if (_form == _unit_form::own || _form == _unit_form::power) {
887 const auto unit = Traits::own_unit(_form == _unit_form::power);
888 auto out = ctx.out();
889 if (_has_spec) {
890 out = _num.format(static_cast<double>(q.count()), ctx);
891 } else if constexpr (std::floating_point<typename Q::rep>) {
892 out = _format_append(out, _fixed_string(q.count()).c_str());
893 } else {
894 out = std::format_to(out, "{}", q.count());
895 }
896 return Traits::append_own_unit(out, unit);
897 }
898 }
899 // Without a unit of its own, the own form renders in the base unit.
900 const auto d = _form == _unit_form::scaled ? Traits::scaled_display(q) : Traits::base_display(q);
901 auto out = _has_spec ? _num.format(d.value, ctx) : _format_append(ctx.out(), _fixed_string(d.value).c_str());
902 return Traits::append_label(out, d);
903 }
904};
905#endif
906
907} // namespace detail
910} // namespace quanta
quantity< watt_unit, Rep, Precision > power
Power quantity with configurable representation and precision.
Definition electro.hpp:900
Type-safe units modeled after std::chrono.
Definition core.hpp:90
constexpr scaled_t scaled
Selects the scaled form in to_string().
Definition core.hpp:161
constexpr base_unit_t base_unit
Selects the base-unit form in to_string().
Definition core.hpp:140
constexpr bool treat_as_inexact_v
Definition core.hpp:119
Tag type selecting the base-unit form, see base_unit.
Definition core.hpp:124
Tag type selecting the scaled form, see scaled.
Definition core.hpp:145
scaled_t()=default
Trait to indicate floating-point-like behavior.
Definition core.hpp:116