blob: 405320bdfa87a182f5dd690180deb4e0a8b63a24 [file]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_NUMERICS_BASIC_OPS_IMPL_H_
#define BASE_NUMERICS_BASIC_OPS_IMPL_H_
#include <algorithm>
#include <array>
#include <bit>
#include <climits>
#include <concepts>
#include <cstdint>
#include <limits>
#include <span>
#include <type_traits>
#include <utility>
namespace base::numerics_internal {
// Matches any unsigned integral type with no padding bits.
//
// Automatically rejects `bool` because `digits == 1` vs `sizeof(bool) * 8 ==
// 8`. Accepts `uint8_t`..`uint64_t`, `size_t`, `char8_t`, `char16_t`,
// `char32_t`, and `char` (on platforms where char is unsigned).
template <typename T>
concept PackedUnsignedIntegral =
std::unsigned_integral<T> &&
(std::numeric_limits<T>::digits == sizeof(T) * CHAR_BIT);
// Matches any signed integral type with no padding bits.
//
// Accepts `int8_t`..`int64_t`, `ptrdiff_t`, and `char` (on platforms where
// char is signed).
template <typename T>
concept PackedSignedIntegral =
std::signed_integral<T> &&
(std::numeric_limits<T>::digits + 1 == sizeof(T) * CHAR_BIT);
// Matches any signed or unsigned integral type with no padding bits.
template <typename T>
concept PackedIntegral = PackedUnsignedIntegral<T> || PackedSignedIntegral<T>;
// Matches IEEE 754 standard binary floating-point types (`float`, `double`,
// `std::float16_t`, `std::float128_t`) with no padding bits.
//
// The formula calculates the total stored bits:
// 1 (sign bit) +
// bit_width(max_exponent - 1) (exponent bits - 1) +
// digits (stored mantissa bits + 1 for implicit leading 1 bit)
//
// This guarantees zero padding bits and inherently rejects non-standard formats
// like x87 80-bit `long double` in 12- or 16-byte containers (as well as 80-bit
// floats with explicit integer bits).
template <typename T>
concept IeeeBinaryFloat =
std::floating_point<T> && std::numeric_limits<T>::is_iec559 &&
(1 + std::bit_width(size_t{std::numeric_limits<T>::max_exponent - 1}) +
std::numeric_limits<T>::digits ==
sizeof(T) * CHAR_BIT);
// Matches any scoped or unscoped enumeration whose underlying integral type
// satisfies `PackedIntegral`.
//
// This accepts standard enums backed by `uint8_t`..`uint64_t` or `int`, but
// rejects enums explicitly backed by `bool` (`enum class E : bool`).
template <typename T>
concept PackedEnum =
std::is_enum_v<T> && PackedIntegral<std::underlying_type_t<T>>;
// Type trait to provide an unsigned int of a desired exact bit size.
// Simplified version of `boost::uint_t<N>::exact`:
// https://www.boost.org/doc/libs/latest/libs/integer/doc/html/boost_integer/integer.html
template <size_t N>
struct uint_exact;
template <>
struct uint_exact<8> {
using type = uint8_t;
};
template <>
struct uint_exact<16> {
using type = uint16_t;
};
template <>
struct uint_exact<32> {
using type = uint32_t;
};
template <>
struct uint_exact<64> {
using type = uint64_t;
};
#if defined(__SIZEOF_INT128__)
template <>
struct uint_exact<128> {
using type = __uint128_t;
};
#endif
template <size_t N>
using uint_exact_t = typename uint_exact<N>::type;
// Reads a value of type `T` from `bytes` with the specified endianness.
template <std::endian Endian, typename T>
requires(PackedUnsignedIntegral<T>)
constexpr T FromEndian(std::span<const uint8_t, sizeof(T)> bytes) {
// std::bit_cast<T, U> requires matching sizeof. Thus we materialize the span
// into an array first. At runtime this is completely elided:
// https://godbolt.org/z/cssPcEnG4
std::array<uint8_t, sizeof(T)> arr;
std::ranges::copy(bytes, arr.begin());
T val = std::bit_cast<T>(arr);
return Endian == std::endian::native ? val : std::byteswap(val);
}
template <std::endian Endian, typename T>
requires(PackedSignedIntegral<T>)
constexpr T FromEndian(std::span<const uint8_t, sizeof(T)> bytes) {
return static_cast<T>(FromEndian<Endian, std::make_unsigned_t<T>>(bytes));
}
template <std::endian Endian, typename T>
requires(IeeeBinaryFloat<T>)
constexpr T FromEndian(std::span<const uint8_t, sizeof(T)> bytes) {
return std::bit_cast<T>(
FromEndian<Endian, uint_exact_t<sizeof(T) * 8>>(bytes));
}
template <std::endian Endian, typename T>
requires(PackedEnum<T>)
constexpr T FromEndian(std::span<const uint8_t, sizeof(T)> bytes) {
return static_cast<T>(FromEndian<Endian, std::underlying_type_t<T>>(bytes));
}
// Converts `val` of type `T` to a byte array with the specified endianness.
template <std::endian Endian, typename T>
requires(PackedUnsignedIntegral<T>)
constexpr std::array<uint8_t, sizeof(T)> ToEndian(T val) {
return std::bit_cast<std::array<uint8_t, sizeof(T)>>(
Endian == std::endian::native ? val : std::byteswap(val));
}
template <std::endian Endian, typename T>
requires(PackedSignedIntegral<T>)
constexpr std::array<uint8_t, sizeof(T)> ToEndian(T val) {
return ToEndian<Endian>(static_cast<std::make_unsigned_t<T>>(val));
}
template <std::endian Endian, typename T>
requires(IeeeBinaryFloat<T>)
constexpr std::array<uint8_t, sizeof(T)> ToEndian(T val) {
return ToEndian<Endian>(std::bit_cast<uint_exact_t<sizeof(T) * 8>>(val));
}
template <std::endian Endian, typename T>
requires(PackedEnum<T>)
constexpr std::array<uint8_t, sizeof(T)> ToEndian(T val) {
return ToEndian<Endian>(std::to_underlying(val));
}
} // namespace base::numerics_internal
#endif // BASE_NUMERICS_BASIC_OPS_IMPL_H_