Unit Conversion and Dimensional Analysis Library 3.6.1
A compile-time, header-only C++23 dimensional-analysis library
Loading...
Searching...
No Matches
serialization.h
Go to the documentation of this file.
1//--------------------------------------------------------------------------------------------------
2//
3// UnitConversion: A compile-time c++23 unit conversion library with no dependencies
4//
5//--------------------------------------------------------------------------------------------------
6//
7// The MIT License (MIT)
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
10// and associated documentation files (the "Software"), to deal in the Software without
11// restriction, including without limitation the rights to use, copy, modify, merge, publish,
12// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
13// Software is furnished to do so, subject to the following conditions:
14//
15// The above copyright notice and this permission notice shall be included in all copies or
16// substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
19// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
21// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23//
24//--------------------------------------------------------------------------------------------------
25//
26// Copyright (c) 2016 Nic Holthaus
27//
28//--------------------------------------------------------------------------------------------------
29//
32//
33//--------------------------------------------------------------------------------------------------
34
35#pragma once
36
37#ifndef units_serialization_h_
38#define units_serialization_h_
39
40#include <array>
41#include <cmath>
42#include <compare>
43#include <cstddef>
44#include <cstdint>
45#include <cstdio>
46#include <cstring>
47#include <expected>
48#include <functional>
49#include <istream>
50#include <iterator>
51#include <limits>
52#include <memory>
53#include <ostream>
54#include <span>
55#include <stdexcept>
56#include <string>
57#include <string_view>
58#include <tuple>
59#include <units/core.h>
60#include <utility>
61#include <vector>
62
63namespace units
64{
65 namespace detail
66 {
73 using builtin_dimensions = std::tuple<dimension::length, dimension::mass, dimension::time, dimension::current, dimension::temperature, dimension::substance, dimension::luminous_intensity,
80
81 //------------------------------------------------------------------------------------------------------------------
82 // FUNCTION: name_hash [static]
83 //------------------------------------------------------------------------------------------------------------------
90 //------------------------------------------------------------------------------------------------------------------
91 constexpr std::uint64_t name_hash(std::string_view name) noexcept
92 {
93 std::uint64_t h = 1469598103934665603ULL;
94 for (const char c : name)
95 {
96 h ^= static_cast<std::uint8_t>(c);
97 h *= 1099511628211ULL;
98 }
99 return h;
100 }
101 } // namespace detail
102
112
118 class any_unit;
119
120 //======================================================================================================================
121 // VARINT (LEB128) — the terse integer codec
122 //======================================================================================================================
123
124 namespace detail
125 {
126 //------------------------------------------------------------------------------------------------------------------
127 // FUNCTION: put_uvarint [static]
128 //------------------------------------------------------------------------------------------------------------------
132 //------------------------------------------------------------------------------------------------------------------
133 inline void put_uvarint(std::vector<std::byte>& out, std::uint64_t value)
134 {
135 do
136 {
137 std::uint8_t byte = value & 0x7F;
138 value >>= 7;
139 if (value != 0)
140 byte |= 0x80;
141 out.push_back(std::byte{byte});
142 } while (value != 0);
143 }
144
145 //------------------------------------------------------------------------------------------------------------------
146 // FUNCTION: put_svarint [static]
147 //------------------------------------------------------------------------------------------------------------------
151 //------------------------------------------------------------------------------------------------------------------
152 inline void put_svarint(std::vector<std::byte>& out, std::int64_t value)
153 {
154 put_uvarint(out, (static_cast<std::uint64_t>(value) << 1) ^ static_cast<std::uint64_t>(value >> 63));
155 }
156
157 //------------------------------------------------------------------------------------------------------------------
158 // FUNCTION: get_uvarint [static]
159 //------------------------------------------------------------------------------------------------------------------
165 //------------------------------------------------------------------------------------------------------------------
166 inline bool get_uvarint(const std::byte*& cursor, const std::byte* end, std::uint64_t& value)
167 {
168 value = 0;
169 unsigned int shift = 0;
170 while (cursor != end)
171 {
172 const auto byte = std::to_integer<std::uint8_t>(*cursor++);
173 value |= static_cast<std::uint64_t>(byte & 0x7F) << shift;
174 if ((byte & 0x80) == 0)
175 return true;
176 shift += 7;
177 if (shift >= 64)
178 return false;
179 }
180 return false;
181 }
182
183 //------------------------------------------------------------------------------------------------------------------
184 // FUNCTION: get_svarint [static]
185 //------------------------------------------------------------------------------------------------------------------
191 //------------------------------------------------------------------------------------------------------------------
192 inline bool get_svarint(const std::byte*& cursor, const std::byte* end, std::int64_t& value)
193 {
194 std::uint64_t raw;
195 if (!get_uvarint(cursor, end, raw))
196 return false;
197 value = static_cast<std::int64_t>((raw >> 1) ^ (~(raw & 1) + 1));
198 return true;
199 }
200 } // namespace detail
201
202 //======================================================================================================================
203 // UNIT IDENTITY — the compile-time dimension signature, and its runtime form
204 //======================================================================================================================
205
210 {
211 std::uint64_t hash;
212 std::int64_t num;
213 std::int64_t den;
214 };
215
220 {
221 std::vector<dimension_term> terms;
222
223 //------------------------------------------------------------------------------------------------------------------
224 // FUNCTION: operator== [public]
225 //------------------------------------------------------------------------------------------------------------------
229 //------------------------------------------------------------------------------------------------------------------
230 bool operator==(const unit_identity& other) const noexcept
231 {
232 if (terms.size() != other.terms.size())
233 return false;
234 for (std::size_t i = 0; i < terms.size(); ++i)
235 if (terms[i].hash != other.terms[i].hash || terms[i].num != other.terms[i].num || terms[i].den != other.terms[i].den)
236 return false;
237 return true;
238 }
239 };
240
241 namespace detail
242 {
243 //------------------------------------------------------------------------------------------------------------------
244 // FUNCTION: dimension_arity [static]
245 //------------------------------------------------------------------------------------------------------------------
249 //------------------------------------------------------------------------------------------------------------------
250 template<class DimensionList>
251 consteval std::size_t dimension_arity()
252 {
253 if constexpr (DimensionList::empty)
254 return 0;
255 else
257 }
258
259 //------------------------------------------------------------------------------------------------------------------
260 // FUNCTION: fill_terms [static]
261 //------------------------------------------------------------------------------------------------------------------
269 //------------------------------------------------------------------------------------------------------------------
270 template<class DimensionList, std::size_t N>
271 consteval void fill_terms(std::array<dimension_term, N>& out, std::size_t at)
272 {
273 if constexpr (!DimensionList::empty)
274 {
275 using front_dim = typename DimensionList::front;
276 using tag = typename front_dim::dimension;
277 using exponent = typename front_dim::exponent;
278 out[at] = dimension_term{name_hash(std::string_view(tag::name)), exponent::num, exponent::den};
280 }
281 }
282
285 template<UnitType Unit>
287 {
289 static constexpr std::size_t arity = dimension_arity<Dim>();
290
291 static consteval std::array<dimension_term, arity> compute()
292 {
293 std::array<dimension_term, arity> terms{};
294 fill_terms<Dim, arity>(terms, 0);
295 // insertion sort by hash — arity is tiny (the base dimensions of one quantity)
296 for (std::size_t i = 1; i < arity; ++i)
297 {
298 dimension_term key = terms[i];
299 std::size_t j = i;
300 while (j > 0 && terms[j - 1].hash > key.hash)
301 {
302 terms[j] = terms[j - 1];
303 --j;
304 }
305 terms[j] = key;
306 }
307 return terms;
308 }
309
310 static constexpr std::array<dimension_term, arity> value = compute();
311 };
312
313 //------------------------------------------------------------------------------------------------------------------
314 // FUNCTION: identity_of [static]
315 //------------------------------------------------------------------------------------------------------------------
320 //------------------------------------------------------------------------------------------------------------------
321 template<UnitType Unit>
323 {
324 unit_identity id;
325 id.terms.assign(signature<Unit>::value.begin(), signature<Unit>::value.end());
326 return id;
327 }
328
330 template<class Dim>
332 } // namespace detail
333
334 //======================================================================================================================
335 // WIRE FORMAT
336 //======================================================================================================================
337
338 namespace detail
339 {
340 inline constexpr std::uint8_t serialization_version = 1;
341
343 enum class value_kind : std::uint8_t
344 {
346 f32 = 1,
347 f64 = 2
348 };
349
350 //------------------------------------------------------------------------------------------------------------------
351 // FUNCTION: encode [static]
352 //------------------------------------------------------------------------------------------------------------------
362 //------------------------------------------------------------------------------------------------------------------
363 inline std::vector<std::byte> encode(const unit_identity& identity, double base)
364 {
366 if (base == std::floor(base) && std::abs(base) < 9.0e15)
368 else if (static_cast<double>(static_cast<float>(base)) == base)
370 else
372
373 // any fractional exponent forces the fracExp flag
374 bool fracExp = false;
375 for (const auto& term : identity.terms)
376 if (term.den != 1)
377 fracExp = true;
378
379 std::vector<std::byte> out;
380 out.push_back(std::byte{serialization_version});
381 const std::uint8_t header = static_cast<std::uint8_t>(static_cast<std::uint8_t>(kind) | (fracExp ? 0x04 : 0x00));
382 out.push_back(std::byte{header});
383 put_uvarint(out, identity.terms.size());
384 for (const auto& term : identity.terms)
385 {
386 // base dimension keyed by an 8-byte name-hash: fixed size, no central table, any dimension round-trips
387 for (unsigned int i = 0; i < 8; ++i)
388 out.push_back(std::byte{static_cast<std::uint8_t>(term.hash >> (8 * i))});
389 put_svarint(out, term.num);
390 if (fracExp)
391 put_svarint(out, term.den);
392 }
393
394 switch (kind)
395 {
396 case value_kind::ivarint: put_svarint(out, static_cast<std::int64_t>(base)); break;
397 case value_kind::f32:
398 {
399 const float f = static_cast<float>(base);
400 std::uint32_t bits;
401 std::memcpy(&bits, &f, sizeof(bits));
402 for (unsigned int i = 0; i < 4; ++i)
403 out.push_back(std::byte{static_cast<std::uint8_t>(bits >> (8 * i))});
404 break;
405 }
406 case value_kind::f64:
407 {
408 std::uint64_t bits;
409 std::memcpy(&bits, &base, sizeof(bits));
410 for (unsigned int i = 0; i < 8; ++i)
411 out.push_back(std::byte{static_cast<std::uint8_t>(bits >> (8 * i))});
412 break;
413 }
414 }
415 return out;
416 }
417 } // namespace detail
418
419 //======================================================================================================================
420 // any_unit
421 //======================================================================================================================
422
424 {
425 public:
426 //------------------------------------------------------------------------------------------------------------------
427 // FUNCTION: any_unit [public]
428 //------------------------------------------------------------------------------------------------------------------
435 //------------------------------------------------------------------------------------------------------------------
436 any_unit(unit_identity id, double base)
437 : m_identity(std::move(id)),
438 m_base(base),
439 m_bytes(detail::encode(m_identity, base))
440 {
441 }
442
443 //------------------------------------------------------------------------------------------------------------------
444 // FUNCTION: any_unit [public]
445 //------------------------------------------------------------------------------------------------------------------
449 //------------------------------------------------------------------------------------------------------------------
451 : m_identity(),
452 m_base(0.0),
453 m_bytes(detail::encode(m_identity, 0.0))
454 {
455 }
456
457 //------------------------------------------------------------------------------------------------------------------
458 // FUNCTION: is [public]
459 //------------------------------------------------------------------------------------------------------------------
463 //------------------------------------------------------------------------------------------------------------------
464 template<class Dimension>
465 [[nodiscard]] bool is() const noexcept
466 {
468 }
469
470 //------------------------------------------------------------------------------------------------------------------
471 // FUNCTION: value_in_base [public]
472 //------------------------------------------------------------------------------------------------------------------
475 //------------------------------------------------------------------------------------------------------------------
476 [[nodiscard]] double value_in_base() const noexcept
477 {
478 return m_base;
479 }
480
481 //------------------------------------------------------------------------------------------------------------------
482 // FUNCTION: identity [public]
483 //------------------------------------------------------------------------------------------------------------------
486 //------------------------------------------------------------------------------------------------------------------
487 [[nodiscard]] const unit_identity& identity() const noexcept
488 {
489 return m_identity;
490 }
491
492 //======================================================================================================================
493 // BYTES — the owned serialized form, in both a type-safe and a C-interface view
494 //======================================================================================================================
495
496 //------------------------------------------------------------------------------------------------------------------
497 // FUNCTION: bytes [public]
498 //------------------------------------------------------------------------------------------------------------------
503 //------------------------------------------------------------------------------------------------------------------
504 [[nodiscard]] std::span<const std::byte> bytes() const noexcept
505 {
506 return m_bytes;
507 }
508
509 //------------------------------------------------------------------------------------------------------------------
510 // FUNCTION: operator std::span<const std::byte> [public]
511 //------------------------------------------------------------------------------------------------------------------
516 //------------------------------------------------------------------------------------------------------------------
517 [[nodiscard]] operator std::span<const std::byte>() const noexcept
518 {
519 return m_bytes;
520 }
521
522 //------------------------------------------------------------------------------------------------------------------
523 // FUNCTION: data [public]
524 //------------------------------------------------------------------------------------------------------------------
530 //------------------------------------------------------------------------------------------------------------------
531 [[nodiscard]] const char* data() const noexcept
532 {
533 return reinterpret_cast<const char*>(m_bytes.data());
534 }
535
536 //------------------------------------------------------------------------------------------------------------------
537 // FUNCTION: size [public]
538 //------------------------------------------------------------------------------------------------------------------
541 //------------------------------------------------------------------------------------------------------------------
542 [[nodiscard]] std::size_t size() const noexcept
543 {
544 return m_bytes.size();
545 }
546
547 //------------------------------------------------------------------------------------------------------------------
548 // FUNCTION: to_string [public]
549 //------------------------------------------------------------------------------------------------------------------
558 //------------------------------------------------------------------------------------------------------------------
559 [[nodiscard]] std::string to_string() const
560 {
561 std::string named;
562 // resolve to the canonical named unit of whichever known dimension matches, and render it exactly as a
563 // concrete unit streams (name/abbreviation + dimension form); leave `named` empty if no known dimension matched
564 try
565 {
566 visit([&named](const auto& quantity) { named = units::to_string(quantity); });
567 }
568 catch (const std::runtime_error&)
569 {
570 }
571 return named.empty() ? to_string_raw() : named;
572 }
573
574 //------------------------------------------------------------------------------------------------------------------
575 // FUNCTION: to_string_raw [public]
576 //------------------------------------------------------------------------------------------------------------------
584 //------------------------------------------------------------------------------------------------------------------
585 [[nodiscard]] std::string to_string_raw() const
586 {
587 std::string out = std::to_string(m_base);
588 if (m_identity.terms.empty())
589 {
590 out += " [dimensionless]";
591 return out;
592 }
593 out += " [";
594 for (std::size_t i = 0; i < m_identity.terms.size(); ++i)
595 {
596 if (i != 0)
597 out += ' ';
598 char hex[19];
599 std::snprintf(hex, sizeof(hex), "#%llx", static_cast<unsigned long long>(m_identity.terms[i].hash));
600 out += hex;
601 out += '^';
602 out += std::to_string(m_identity.terms[i].num);
603 if (m_identity.terms[i].den != 1)
604 {
605 out += '/';
606 out += std::to_string(m_identity.terms[i].den);
607 }
608 }
609 out += ']';
610 return out;
611 }
612
613 //======================================================================================================================
614 // COMPARISON
615 //======================================================================================================================
616
617 //------------------------------------------------------------------------------------------------------------------
618 // FUNCTION: operator== [public]
619 //------------------------------------------------------------------------------------------------------------------
629 //------------------------------------------------------------------------------------------------------------------
630 [[nodiscard]] bool operator==(const any_unit& other) const noexcept
631 {
632 if (!(m_identity == other.m_identity))
633 return false;
634 const double diff = std::abs(m_base - other.m_base);
635 return diff < std::numeric_limits<double>::epsilon() * std::abs(m_base + other.m_base) || diff < std::numeric_limits<double>::min();
636 }
637
638 //------------------------------------------------------------------------------------------------------------------
639 // FUNCTION: operator!= [public]
640 //------------------------------------------------------------------------------------------------------------------
644 //------------------------------------------------------------------------------------------------------------------
645 [[nodiscard]] bool operator!=(const any_unit& other) const noexcept
646 {
647 return !(*this == other);
648 }
649
650 //------------------------------------------------------------------------------------------------------------------
651 // FUNCTION: operator<=> [public]
652 //------------------------------------------------------------------------------------------------------------------
660 //------------------------------------------------------------------------------------------------------------------
661 [[nodiscard]] std::partial_ordering operator<=>(const any_unit& other) const noexcept
662 {
663 if (!(m_identity == other.m_identity))
664 return std::partial_ordering::unordered;
665 if (*this == other)
666 return std::partial_ordering::equivalent;
667 return m_base <=> other.m_base;
668 }
669
670 //------------------------------------------------------------------------------------------------------------------
671 // FUNCTION: to [public]
672 //------------------------------------------------------------------------------------------------------------------
676 //------------------------------------------------------------------------------------------------------------------
677 template<class Unit>
678 [[nodiscard]] std::expected<Unit, deserialize_error> to() const
679 {
680 static_assert(traits::is_unit_v<Unit>,
681 "any_unit::to<T>() collapses into a unit type (e.g. meters<double>), not a bare number. To read a plain value, collapse to a unit first, then call .value() or .to<double>() on that "
682 "unit.");
683 // gate the body so a non-unit Unit produces ONLY the friendly message above, no downstream template soup
684 if constexpr (traits::is_unit_v<Unit>)
685 {
686 if (m_identity != detail::identity_of<Unit>())
687 return std::unexpected(deserialize_error::dimension_mismatch);
688
689 using ConversionFactor = typename traits::unit_traits<Unit>::conversion_factor;
691 using UnderlyingTarget = typename traits::unit_traits<Unit>::underlying_type;
692
693 // Express the SI-base magnitude in the TARGET unit's scale, all in double so no lossy unit conversion is
694 // attempted: a double-underlying instance of the target unit converts from the canonical base cleanly.
695 using TargetAsDouble = unit<traits::strong_t<ConversionFactor>, double, typename traits::unit_traits<Unit>::numerical_scale_type>;
696 const double as_double = TargetAsDouble(detail::canonical_unit_t<Dim>(m_base)).template to<double>();
697
698 // Narrow to the target's underlying type. An integral target that cannot represent the value exactly is
699 // a lossy_target error rather than a silent truncation.
700 if constexpr (!std::is_floating_point_v<UnderlyingTarget>)
701 {
702 if (as_double != std::floor(as_double) || std::abs(as_double) > static_cast<double>(std::numeric_limits<UnderlyingTarget>::max()))
703 return std::unexpected(deserialize_error::lossy_target);
704 }
705 return Unit(static_cast<UnderlyingTarget>(as_double));
706 }
707 else
708 {
709 return std::unexpected(deserialize_error::dimension_mismatch); // unreachable; the static_assert fired
710 }
711 }
712
713 //------------------------------------------------------------------------------------------------------------------
714 // FUNCTION: try_to [public]
715 //------------------------------------------------------------------------------------------------------------------
719 //------------------------------------------------------------------------------------------------------------------
720 template<class Unit>
721 [[nodiscard]] Unit try_to() const
722 {
723 static_assert(traits::is_unit_v<Unit>, "any_unit::try_to<T>() collapses into a unit type (e.g. meters<double>), not a bare number.");
724 auto result = to<Unit>();
725 if (!result)
726 throw std::runtime_error("units::any_unit: dimension mismatch collapsing to the requested unit");
727 return *result;
728 }
729
730 //------------------------------------------------------------------------------------------------------------------
731 // FUNCTION: assign_to [public]
732 //------------------------------------------------------------------------------------------------------------------
745 //------------------------------------------------------------------------------------------------------------------
746 template<class Unit>
747 bool assign_to(Unit& out) const
748 {
749 static_assert(traits::is_unit_v<Unit>, "any_unit::assign_to(out) assigns into a unit variable (e.g. meters<double>), not a bare number. Collapse to a unit, then read its value.");
750 if (auto result = to<Unit>())
751 {
752 out = *result;
753 return true;
754 }
755 return false;
756 }
757
758 //------------------------------------------------------------------------------------------------------------------
759 // FUNCTION: visit [public]
760 //------------------------------------------------------------------------------------------------------------------
772 //------------------------------------------------------------------------------------------------------------------
773 template<class... Dimensions, class Visitor>
774 void visit(Visitor&& visitor) const
775 {
776 // bind to a named lvalue so the traversal passes it through by reference (never moving it), and the
777 // single invocation site (try_dispatch_one) is the only place it is used as the caller's value category
778 bool matched;
779 if constexpr (sizeof...(Dimensions) == 0)
780 matched = dispatch_tuple<detail::builtin_dimensions>(std::forward<Visitor>(visitor));
781 else
782 matched = dispatch_list<Dimensions...>(std::forward<Visitor>(visitor));
783 if (!matched)
784 throw std::runtime_error("units::any_unit: no candidate dimension matched the stream");
785 }
786
787 private:
788 //------------------------------------------------------------------------------------------------------------------
789 // FUNCTION: try_dispatch_one [private]
790 //------------------------------------------------------------------------------------------------------------------
796 //------------------------------------------------------------------------------------------------------------------
797 template<class Dimension, class Visitor>
798 bool try_dispatch_one(Visitor&& visitor) const
799 {
801 if (m_identity == detail::identity_of<Base>())
802 {
803 std::forward<Visitor>(visitor)(Base(m_base));
804 return true;
805 }
806 return false;
807 }
808
812 template<class... Dimensions, class Visitor>
813 bool dispatch_list(Visitor&& visitor) const
814 {
815 bool matched = false;
816 // fold in order; stop invoking once matched
817 ((matched = matched || try_dispatch_one<Dimensions>(visitor)), ...);
818 return matched;
819 }
820
823 template<class DimTuple, std::size_t I = 0, class Visitor>
824 bool dispatch_tuple(Visitor&& visitor) const
825 {
826 if constexpr (I < std::tuple_size_v<DimTuple>)
827 {
828 if (try_dispatch_one<std::tuple_element_t<I, DimTuple>>(visitor))
829 return true;
830 return dispatch_tuple<DimTuple, I + 1>(visitor);
831 }
832 return false;
833 }
834
835 unit_identity m_identity;
836 double m_base;
837 std::vector<std::byte> m_bytes;
838 };
839
840 //----------------------------------------------------------------------------------------------------------------------
841 // FUNCTION: operator<< [public]
842 //----------------------------------------------------------------------------------------------------------------------
850 //----------------------------------------------------------------------------------------------------------------------
851 inline std::ostream& operator<<(std::ostream& os, const any_unit& value)
852 {
853 os.write(value.data(), static_cast<std::streamsize>(value.size()));
854 return os;
855 }
856
857 //======================================================================================================================
858 // unit_cast — reclaimed: the explicit throwing collapse from any_unit to a concrete unit
859 //======================================================================================================================
860
861 //----------------------------------------------------------------------------------------------------------------------
862 // FUNCTION: unit_cast [public]
863 //----------------------------------------------------------------------------------------------------------------------
870 //----------------------------------------------------------------------------------------------------------------------
871 template<class Target>
872 [[nodiscard]] Target unit_cast(const any_unit& value)
873 {
874 static_assert(traits::is_unit_v<Target>, "units::unit_cast<T>(any_unit) casts to a unit type (e.g. meters<double>), not a bare number. Collapse to a unit, then read its value.");
875 return value.try_to<Target>();
876 }
877
878 //======================================================================================================================
879 // serialize / deserialize
880 //======================================================================================================================
881
882 //----------------------------------------------------------------------------------------------------------------------
883 // FUNCTION: serialize [public]
884 //----------------------------------------------------------------------------------------------------------------------
893 //----------------------------------------------------------------------------------------------------------------------
894 template<class Unit>
895 [[nodiscard]] any_unit serialize(const Unit& quantity)
896 {
897 static_assert(traits::is_unit_v<Unit>, "units::serialize requires a units quantity (e.g. meters<double>). Its argument is not a unit type; wrap the value in a unit before serializing.");
898 // gate the body so a non-unit argument produces ONLY the friendly message above, no downstream template soup
899 if constexpr (!traits::is_unit_v<Unit>)
900 return any_unit{unit_identity{}, 0.0};
901 else
902 {
903 constexpr auto& sig = detail::signature<Unit>::value; // fixed-array compile-time signature (sorted by hash)
904
905 // value in SI canonical base
908 const double base = Base(quantity).value();
909
910 // lift the compile-time signature into the runtime identity, then let any_unit own its encoded form
911 unit_identity id;
912 id.terms.assign(sig.begin(), sig.end());
913
914 return any_unit{std::move(id), base};
915 }
916 }
917
918 //----------------------------------------------------------------------------------------------------------------------
919 // FUNCTION: deserialize [public]
920 //----------------------------------------------------------------------------------------------------------------------
927 //----------------------------------------------------------------------------------------------------------------------
928 [[nodiscard]] inline std::expected<any_unit, deserialize_error> deserialize(std::span<const std::byte> bytes)
929 {
930 const std::byte* cursor = bytes.data();
931 const std::byte* end = bytes.data() + bytes.size();
932
933 if (cursor == end)
934 return std::unexpected(deserialize_error::truncated);
935 const std::uint8_t version = std::to_integer<std::uint8_t>(*cursor++);
936 if (version != detail::serialization_version)
937 return std::unexpected(deserialize_error::bad_version);
938
939 if (cursor == end)
940 return std::unexpected(deserialize_error::truncated);
941 const std::uint8_t header = std::to_integer<std::uint8_t>(*cursor++);
942 const auto kind = static_cast<detail::value_kind>(header & 0x03);
943 const bool fracExp = (header & 0x04) != 0;
944
945 std::uint64_t count = 0;
946 if (!detail::get_uvarint(cursor, end, count))
947 return std::unexpected(deserialize_error::truncated);
948
949 unit_identity id;
950 id.terms.reserve(count);
951 for (std::uint64_t i = 0; i < count; ++i)
952 {
953 if (end - cursor < 8)
954 return std::unexpected(deserialize_error::truncated);
955 std::uint64_t hash = 0;
956 for (unsigned int byteIndex = 0; byteIndex < 8; ++byteIndex)
957 hash |= static_cast<std::uint64_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * byteIndex);
958 std::int64_t num = 0;
959 std::int64_t den = 1;
960 if (!detail::get_svarint(cursor, end, num))
961 return std::unexpected(deserialize_error::truncated);
962 if (fracExp && !detail::get_svarint(cursor, end, den))
963 return std::unexpected(deserialize_error::truncated);
964 id.terms.push_back(dimension_term{hash, num, den});
965 }
966
967 double base = 0.0;
968 switch (kind)
969 {
971 {
972 std::int64_t v;
973 if (!detail::get_svarint(cursor, end, v))
974 return std::unexpected(deserialize_error::truncated);
975 base = static_cast<double>(v);
976 break;
977 }
979 {
980 if (end - cursor < 4)
981 return std::unexpected(deserialize_error::truncated);
982 std::uint32_t bits = 0;
983 for (unsigned int i = 0; i < 4; ++i)
984 bits |= static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * i);
985 float f;
986 std::memcpy(&f, &bits, sizeof(f));
987 base = static_cast<double>(f);
988 break;
989 }
991 {
992 if (end - cursor < 8)
993 return std::unexpected(deserialize_error::truncated);
994 std::uint64_t bits = 0;
995 for (unsigned int i = 0; i < 8; ++i)
996 bits |= static_cast<std::uint64_t>(std::to_integer<std::uint8_t>(*cursor++)) << (8 * i);
997 std::memcpy(&base, &bits, sizeof(base));
998 break;
999 }
1000 default: return std::unexpected(deserialize_error::bad_version);
1001 }
1002
1003 return any_unit(std::move(id), base);
1004 }
1005
1006 //----------------------------------------------------------------------------------------------------------------------
1007 // FUNCTION: deserialize [public]
1008 //----------------------------------------------------------------------------------------------------------------------
1013 //----------------------------------------------------------------------------------------------------------------------
1014 template<class Unit>
1015 [[nodiscard]] std::expected<Unit, deserialize_error> deserialize(std::span<const std::byte> bytes)
1016 {
1017 static_assert(traits::is_unit_v<Unit>,
1018 "units::deserialize<T>(bytes) decodes into a unit type (e.g. deserialize<meters<double>>). The requested type is not a unit; use deserialize(bytes) for an erased any_unit.");
1019 auto erased = deserialize(bytes);
1020 if (!erased)
1021 return std::unexpected(erased.error());
1022 return erased->template to<Unit>();
1023 }
1024
1025 //----------------------------------------------------------------------------------------------------------------------
1026 // FUNCTION: deserialize [public]
1027 //----------------------------------------------------------------------------------------------------------------------
1037 //----------------------------------------------------------------------------------------------------------------------
1038 [[nodiscard]] inline std::expected<any_unit, deserialize_error> deserialize(std::istream& is)
1039 {
1040 const std::istream::pos_type start = is.tellg();
1041 if (start == std::istream::pos_type(-1))
1042 return std::unexpected(deserialize_error::truncated); // not seekable: records can't be self-delimited here
1043
1044 const std::vector<char> buffer{std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>()};
1045 is.clear(); // the drain set eofbit; clear it so a good decode leaves the stream usable
1046
1047 auto decoded = deserialize(std::span<const std::byte>(reinterpret_cast<const std::byte*>(buffer.data()), buffer.size()));
1048 if (decoded)
1049 is.seekg(start + static_cast<std::istream::off_type>(decoded->size())); // rewind past exactly this record
1050 return decoded;
1051 }
1052
1053 //----------------------------------------------------------------------------------------------------------------------
1054 // FUNCTION: operator>> [public]
1055 //----------------------------------------------------------------------------------------------------------------------
1064 //----------------------------------------------------------------------------------------------------------------------
1065 inline std::istream& operator>>(std::istream& is, any_unit& value)
1066 {
1067 if (auto decoded = deserialize(is))
1068 value = std::move(*decoded);
1069 else
1070 is.setstate(std::ios::failbit);
1071 return is;
1072 }
1073
1074 //----------------------------------------------------------------------------------------------------------------------
1075 // FUNCTION: deserialize [public]
1076 //----------------------------------------------------------------------------------------------------------------------
1085 //----------------------------------------------------------------------------------------------------------------------
1086 template<class Unit>
1087 [[nodiscard]] std::expected<Unit, deserialize_error> deserialize(std::istream& is)
1088 {
1089 static_assert(traits::is_unit_v<Unit>,
1090 "units::deserialize<T>(stream) decodes into a unit type (e.g. deserialize<meters<double>>). The requested type is not a unit; use deserialize(stream) for an erased any_unit.");
1091 auto erased = deserialize(is);
1092 if (!erased)
1093 return std::unexpected(erased.error());
1094 return erased->template to<Unit>();
1095 }
1096} // namespace units
1097
1098//----------------------------------------------------------------------------------------------------------------------
1099// std::hash<units::any_unit>
1100//----------------------------------------------------------------------------------------------------------------------
1104template<>
1105struct std::hash<units::any_unit>
1106{
1107 std::size_t operator()(const units::any_unit& value) const noexcept
1108 {
1109 // FNV-1a-style fold over the term signature, mixed with the base-value hash
1110 std::size_t seed = std::hash<double>()(value.value_in_base());
1111 const auto mix = [&seed](std::size_t h) noexcept { seed ^= h + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); };
1112 for (const auto& term : value.identity().terms)
1113 {
1114 mix(std::hash<std::uint64_t>()(term.hash));
1115 mix(std::hash<std::int64_t>()(term.num));
1116 mix(std::hash<std::int64_t>()(term.den));
1117 }
1118 return seed;
1119 }
1120};
1121
1122#endif // units_serialization_h_
Definition serialization.h:424
bool assign_to(Unit &out) const
collapses into an existing unit variable, leaving it untouched on a dimension mismatch
Definition serialization.h:747
double value_in_base() const noexcept
the magnitude in SI canonical base units, for logging or routing
Definition serialization.h:476
std::expected< Unit, deserialize_error > to() const
collapses into a concrete unit, checked (the safe default)
Definition serialization.h:678
any_unit()
constructs an empty erased quantity (dimensionless, zero) — the target for stream extraction
Definition serialization.h:450
bool is() const noexcept
whether this erased quantity is of the requested dimension
Definition serialization.h:465
bool operator!=(const any_unit &other) const noexcept
whether two erased quantities differ in dimension or magnitude
Definition serialization.h:645
Unit try_to() const
collapses into a concrete unit, throwing on a dimension mismatch
Definition serialization.h:721
bool operator==(const any_unit &other) const noexcept
whether two erased quantities are the same dimension and magnitude
Definition serialization.h:630
any_unit(unit_identity id, double base)
constructs an erased quantity from a decoded identity and SI-base magnitude
Definition serialization.h:436
std::span< const std::byte > bytes() const noexcept
the serialized byte stream, as a type-safe view
Definition serialization.h:504
std::string to_string_raw() const
the dimension-agnostic text rendering, keyed by name-hash — always available, never resolves a name
Definition serialization.h:585
std::string to_string() const
a human-readable text rendering of the erased quantity, for logging and diagnostics
Definition serialization.h:559
std::partial_ordering operator<=>(const any_unit &other) const noexcept
orders two erased quantities of the same dimension by magnitude
Definition serialization.h:661
const char * data() const noexcept
a pointer to the serialized bytes as const char*, for byte-oriented interfaces
Definition serialization.h:531
const unit_identity & identity() const noexcept
the decoded dimension signature
Definition serialization.h:487
std::size_t size() const noexcept
the number of serialized bytes
Definition serialization.h:542
void visit(Visitor &&visitor) const
invokes a visitor with the canonical quantity for the decoded dimension
Definition serialization.h:774
Definition core.h:2735
unit, dimensional analysis, generic cmath functions, traits (not dimension-specific),...
@ base
the SI base-dimension list (" m s^-1"); pairs with a base-converted value.
Definition core.h:3419
@ name
the unit's own full name ("meters", "feet"); base-dimension list if unnamed.
Definition core.h:3418
constexpr T unit_cast(const Unit &value) noexcept
Casts an unit to an arithmetic type.
Definition core.h:3754
STL namespace.
constexpr meters_per_second c(299792458.0)
Speed of light in vacuum.
constexpr unit< compound_conversion_factor< joules_, seconds_ > > h(6.62607015e-34)
Planck constant.
make_dimension< length, std::ratio< 2 >, time, std::ratio<-2 > > radioactivity
< Represents an SI derived unit of luminance
Definition core.h:1359
dimension_multiply< pressure, time > dynamic_viscosity
< Represents an SI derived unit of density
Definition core.h:1375
dimension_divide< mass, volume > density
< Represents an SI derived unit of torque
Definition core.h:1374
make_dimension< luminous_intensity, std::ratio< 1 >, length, std::ratio<-2 > > luminance
< Represents an SI derived unit of illuminance
Definition core.h:1358
dimension_pow< angle, std::ratio< 2 > > solid_angle
< Represents a quantity of angle
Definition core.h:1337
dimension_divide< current, voltage > conductance
< Represents an SI derived unit of impedance
Definition core.h:1353
make_dimension< power, std::ratio< 1 >, length, std::ratio<-1 > > spectral_flux
< Represents an SI derived unit of spectral intensity
Definition core.h:1367
dimension_divide< mass, substance > substance_mass
< Represents an SI derived unit of radioactivity
Definition core.h:1360
make_dimension< radiant_intensity, std::ratio< 1 >, area, std::ratio<-1 > > radiance
< Represents an SI derived unit of radiant intensity
Definition core.h:1364
dimension_divide< voltage, current > impedance
< Represents an SI derived unit of capacitance
Definition core.h:1352
dimension_divide< substance, mass > substance_concentration
< Represents an SI derived unit of substance mass
Definition core.h:1361
dimension_divide< energy, time > power
< Represents an SI derived unit of energy
Definition core.h:1349
dimension_multiply< impedance, time > inductance
< Represents an SI derived unit of magnetic flux
Definition core.h:1355
dimension_pow< length, std::ratio< 3 > > volume
< Represents an SI derived unit of area
Definition core.h:1344
dimension_divide< area, time > kinematic_viscosity
< Represents an SI derived unit of dynamic (absolute) viscosity
Definition core.h:1376
make_dimension< angle_tag > angle
< Represents a quantity with no dimension.
Definition core.h:1334
dimension_divide< velocity, time > acceleration
< Represents an SI derived unit of angular velocity
Definition core.h:1341
dimension_multiply< mass, acceleration > force
< Represents an SI derived unit of acceleration
Definition core.h:1342
make_dimension< power, std::ratio< 1 >, solid_angle, std::ratio<-1 > > radiant_intensity
< Represents an SI derived unit of magnetic field strength
Definition core.h:1363
dimension_divide< force, area > pressure
< Represents an SI derived unit of volumetric flow rate
Definition core.h:1346
make_dimension< radiant_intensity, std::ratio< 1 >, length, std::ratio<-1 > > spectral_intensity
< Represents an SI derived unit of irradiance
Definition core.h:1366
dimension_divide< power, current > voltage
< Represents an SI derived unit of power
Definition core.h:1350
dimension_divide< charge, voltage > capacitance
< Represents an SI derived unit of voltage
Definition core.h:1351
dimension_multiply< time, current > charge
< Represents an SI derived unit of pressure
Definition core.h:1347
make_dimension< mass, std::ratio< 1 >, time, std::ratio<-2 >, current, std::ratio<-1 > > magnetic_field_strength
< Represents an SI derived unit of substance concentration
Definition core.h:1362
dimension_divide< energy, current > magnetic_flux
< Represents an SI derived unit of conductance
Definition core.h:1354
dimension_multiply< force, length > energy
< Represents an SI derived unit of charge
Definition core.h:1348
make_dimension< power, std::ratio< 1 >, volume, std::ratio<-1 > > spectral_irradiance
< Represents an SI derived unit of spectral intensity
Definition core.h:1369
make_dimension< volume, std::ratio<-1 > > concentration
< Represents an SI derived unit of energy density
Definition core.h:1378
make_dimension< data_tag > data
< Represents a unit of concentration
Definition core.h:1379
dimension_multiply< solid_angle, luminous_intensity > luminous_flux
< Represents an SI derived unit of inductance
Definition core.h:1356
dimension_divide< length, time > velocity
< Represents an SI derived unit of frequency
Definition core.h:1339
dimension_divide< data, time > data_transfer_rate
< Represents a unit of data size
Definition core.h:1380
dimension_pow< length, std::ratio< 2 > > area
< Represents an SI derived unit of force
Definition core.h:1343
make_dimension< radiant_intensity, std::ratio< 1 >, volume, std::ratio<-1 > > spectral_radiance
< Represents an SI derived unit of spectral flux
Definition core.h:1368
make_dimension< luminous_flux, std::ratio< 1 >, length, std::ratio<-2 > > illuminance
< Represents an SI derived unit of luminous flux
Definition core.h:1357
make_dimension< time, std::ratio<-1 > > frequency
< Represents an SI derived unit of solid angle
Definition core.h:1338
make_dimension< power, std::ratio< 1 >, area, std::ratio<-1 > > irradiance
< Represents an SI derived unit of radiance
Definition core.h:1365
dimension_divide< angle, time > angular_velocity
< Represents an SI derived unit of velocity
Definition core.h:1340
make_dimension< energy, std::ratio< 1 >, volume, std::ratio<-1 > > energy_density
< Represents an SI derived unit of kinematic viscosity
Definition core.h:1377
make_dimension< length, std::ratio< 1 >, time, std::ratio<-3 > > jerk
< Represents an SI derived unit of spectral irradiance
Definition core.h:1372
dimension_divide< volume, time > volume_flow_rate
< Represents an SI derived unit of volume
Definition core.h:1345
dimension_multiply< force, length > torque
< Represents an SI derived unit of jerk
Definition core.h:1373
typename units::detail::dimension_of_impl< U >::type dimension_of_t
Names the dimension_t of a conversion_factor.
Definition core.h:1455
Unit Conversion Library namespace.
Definition units.h:106
std::expected< any_unit, deserialize_error > deserialize(std::span< const std::byte > bytes)
decodes a self-describing byte stream into an erased quantity
Definition serialization.h:928
std::istream & operator>>(std::istream &is, any_unit &value)
reads one self-describing erased quantity from a binary stream (classic stream-extraction form)
Definition serialization.h:1065
any_unit serialize(const Unit &quantity)
serializes a quantity to a self-describing, erased any_unit
Definition serialization.h:895
deserialize_error
the reasons a deserialize can fail
Definition serialization.h:105
@ bad_version
the stream's format version is not understood
Definition serialization.h:107
@ truncated
the byte range ended before a complete quantity was read
Definition serialization.h:106
@ lossy_target
the value cannot be represented in the requested underlying type without loss
Definition serialization.h:110
@ unknown_base_dimension
the stream names a base-dimension code this build does not know
Definition serialization.h:109
@ dimension_mismatch
the stream's dimension does not match the requested target
Definition serialization.h:108
affine::basic_kind< Tag, U > kind
The preferred user-facing spelling of a string-tagged quantity kind: units::kind<"radial",...
Definition kind.h:1214
std::vector< std::byte > encode(const unit_identity &identity, double base)
encodes a dimension identity and SI-base magnitude to the self-describing byte stream
Definition serialization.h:363
void put_uvarint(std::vector< std::byte > &out, std::uint64_t value)
appends an unsigned integer to a byte buffer as an LEB128 varint
Definition serialization.h:133
consteval void fill_terms(std::array< dimension_term, N > &out, std::size_t at)
writes a term per base dimension of a dimension_t<...> list into a fixed span
Definition serialization.h:271
unit< conversion_factor< std::ratio< 1 >, Dim >, double > canonical_unit_t
the canonical SI base unit of a dimension (ratio 1, no pi, no translation)
Definition serialization.h:331
unit_identity identity_of()
the dimension signature of a unit type, as the runtime (vector-backed) identity
Definition serialization.h:322
void put_svarint(std::vector< std::byte > &out, std::int64_t value)
appends a signed integer to a byte buffer as a zig-zag LEB128 varint
Definition serialization.h:152
constexpr std::uint64_t name_hash(std::string_view name) noexcept
FNV-1a 64-bit hash of a base dimension's name.
Definition serialization.h:91
std::tuple< dimension::length, dimension::mass, dimension::time, dimension::current, dimension::temperature, dimension::substance, dimension::luminous_intensity, dimension::angle, dimension::data, dimension::solid_angle, dimension::frequency, dimension::velocity, dimension::angular_velocity, dimension::acceleration, dimension::force, dimension::area, dimension::volume, dimension::volume_flow_rate, dimension::pressure, dimension::charge, dimension::energy, dimension::power, dimension::voltage, dimension::capacitance, dimension::impedance, dimension::conductance, dimension::magnetic_flux, dimension::inductance, dimension::luminous_flux, dimension::illuminance, dimension::luminance, dimension::radioactivity, dimension::substance_mass, dimension::substance_concentration, dimension::magnetic_field_strength, dimension::radiant_intensity, dimension::radiance, dimension::irradiance, dimension::spectral_intensity, dimension::spectral_flux, dimension::spectral_radiance, dimension::spectral_irradiance, dimension::jerk, dimension::torque, dimension::density, dimension::energy_density, dimension::concentration, dimension::data_transfer_rate, dimension::dynamic_viscosity, dimension::kinematic_viscosity > builtin_dimensions
The library's known dimensions, offered to visit as the default candidate set so a stream of any buil...
Definition serialization.h:73
value_kind
header byte layout: [ valueKind:2 | fracExp:1 | reserved:5 ]
Definition serialization.h:344
@ ivarint
value is an integer in SI base, zig-zag varint
Definition serialization.h:345
@ f32
value is an exact 32-bit float
Definition serialization.h:346
@ f64
value is a 64-bit double
Definition serialization.h:347
bool get_uvarint(const std::byte *&cursor, const std::byte *end, std::uint64_t &value)
reads an LEB128 unsigned varint from a byte cursor
Definition serialization.h:166
bool get_svarint(const std::byte *&cursor, const std::byte *end, std::int64_t &value)
reads a zig-zag LEB128 signed varint from a byte cursor
Definition serialization.h:192
consteval std::size_t dimension_arity()
the number of base-dimension terms in a dimension_t<...> list
Definition serialization.h:251
the compile-time signature of a unit as a fixed-size, sorted array of terms
Definition serialization.h:287
one base-dimension term of a signature: which base dimension (by name-hash), and its rational exponen...
Definition serialization.h:210
std::uint64_t hash
FNV-1a hash of the base dimension's name; the wire identity.
Definition serialization.h:211
std::int64_t den
exponent denominator (1 for the common integer-exponent case)
Definition serialization.h:213
std::int64_t num
exponent numerator
Definition serialization.h:212
the runtime identity of a quantity's dimension — the set of nonzero base-dimension terms
Definition serialization.h:220
bool operator==(const unit_identity &other) const noexcept
dimension-signature equality
Definition serialization.h:230