Skip to content

Commit

Permalink
made hi_ and lo_ private members of double_width_int
Browse files Browse the repository at this point in the history
  • Loading branch information
burnpanck committed Nov 6, 2024
1 parent 95cc9f3 commit 5f8eb5c
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 48 deletions.
90 changes: 54 additions & 36 deletions src/core/include/mp-units/bits/fixed_point.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,46 +40,65 @@ import std;

namespace mp_units::detail {

template<typename T>
constexpr std::size_t integer_rep_width_v = std::numeric_limits<std::make_unsigned_t<T>>::digits;


// this class synthesizes a double-width integer from two base-width integers.
template<std::integral T>
struct double_width_int {
static constexpr bool is_signed = std::is_signed_v<T>;
static constexpr std::size_t base_width = std::numeric_limits<std::make_unsigned_t<T>>::digits;
static constexpr std::size_t base_width = integer_rep_width_v<T>;
static constexpr std::size_t width = 2 * base_width;

using Th = T;
using Tl = std::make_unsigned_t<T>;

constexpr double_width_int() = default;

constexpr double_width_int(Th hi_, Tl lo_) : hi(hi_), lo(lo_) {}
private:
constexpr double_width_int(Th hi, Tl lo) : hi_(hi), lo_(lo) {}

template<std::integral>
friend struct double_width_int;

public:
static constexpr double_width_int from_hi_lo(Th hi, Tl lo) { return {hi, lo}; }

explicit constexpr double_width_int(long double v)
{
constexpr auto scale = int_power<long double>(2, base_width);
constexpr auto iscale = 1.l / scale;
auto scaled = v * iscale;
hi = static_cast<Th>(scaled);
auto resid = (scaled - static_cast<long double>(hi));
hi_ = static_cast<Th>(scaled);
auto resid = (scaled - static_cast<long double>(hi_));
if (resid < 0) {
--hi;
--hi_;
resid += 1;
}
lo = static_cast<Tl>(resid * scale);
lo_ = static_cast<Tl>(resid * scale);
}
template<std::integral U>
requires(is_signed || !std::is_signed_v<U>)
explicit(false) constexpr double_width_int(U v)
{
if constexpr (is_signed) {
hi = v < 0 ? Th{-1} : Th{0};
hi_ = v < 0 ? Th{-1} : Th{0};
} else {
hi = 0;
hi_ = 0;
}
lo = static_cast<Tl>(v);
lo_ = static_cast<Tl>(v);
}

explicit constexpr operator Th() const { return static_cast<Th>(lo); }
template<std::integral U>
explicit constexpr operator U() const
{
if constexpr (integer_rep_width_v<U> > base_width) {
return (static_cast<U>(hi_) << base_width) + static_cast<U>(lo_);
} else {
return static_cast<U>(lo_);
}
}

[[nodiscard]] constexpr auto operator<=>(const double_width_int&) const = default;

Expand Down Expand Up @@ -108,10 +127,10 @@ struct double_width_int {
[[nodiscard]] friend constexpr auto operator*(const double_width_int& lhs, Rhs rhs)
{
using RT = std::conditional_t<std::is_signed_v<Rhs>, std::make_signed_t<Tl>, Tl>;
auto lo_prod = double_width_int<RT>::wide_product_of(rhs, lhs.lo);
auto lo_prod = double_width_int<RT>::wide_product_of(rhs, lhs.lo_);
// Normal C++ rules; with respect to signedness, the wider type always wins.
using ret_t = double_width_int<Th>;
return ret_t{static_cast<Th>(lo_prod.hi) + lhs.hi * static_cast<Th>(rhs), lo_prod.lo};
return ret_t{static_cast<Th>(lo_prod.hi_) + lhs.hi_ * static_cast<Th>(rhs), lo_prod.lo_};
}
template<std::integral Lhs>
requires(std::numeric_limits<Lhs>::digits <= base_width)
Expand All @@ -132,19 +151,19 @@ struct double_width_int {
return lhs / static_cast<Tl>(rhs);
}
} else if constexpr (is_signed) {
if (lhs.hi < 0) {
if (lhs.hi_ < 0) {
return -((-lhs) / rhs);
} else {
using unsigned_t = double_width_int<Tl>;
auto tmp = unsigned_t{static_cast<Tl>(lhs.hi), lhs.lo} / rhs;
return ret_t{static_cast<Th>(tmp.hi), tmp.lo};
auto tmp = unsigned_t{static_cast<Tl>(lhs.hi_), lhs.lo_} / rhs;
return ret_t{static_cast<Th>(tmp.hi_), tmp.lo_};
}
} else {
Th res_hi = lhs.hi / rhs;
Th res_hi = lhs.hi_ / rhs;
// unfortunately, wide division is hard: https://en.wikipedia.org/wiki/Division_algorithm.
// Here, we just provide a somewhat naive implementation of long division.
Tl rem_hi = lhs.hi % rhs;
Tl rem_lo = lhs.lo;
Tl rem_hi = lhs.hi_ % rhs;
Tl rem_lo = lhs.lo_;
Tl res_lo = 0;
for (std::size_t i = 0; i < base_width; ++i) {
// shift in one bit
Expand All @@ -165,14 +184,14 @@ struct double_width_int {
requires(std::numeric_limits<Rhs>::digits <= base_width)
[[nodiscard]] friend constexpr double_width_int operator+(const double_width_int& lhs, Rhs rhs)
{
Th rhi = lhs.hi;
Tl rlo = lhs.lo;
Th rhi = lhs.hi_;
Tl rlo = lhs.lo_;
if constexpr (std::is_signed_v<Rhs>) {
// sign extension; no matter if lhs is signed, negative rhs sign extend
if (rhs < 0) --rhi;
}
rlo += static_cast<Tl>(rhs);
if (rlo < lhs.lo) {
if (rlo < lhs.lo_) {
// carry bit
++rhi;
}
Expand All @@ -187,14 +206,14 @@ struct double_width_int {
requires(std::numeric_limits<Rhs>::digits <= base_width)
[[nodiscard]] friend constexpr double_width_int operator-(const double_width_int& lhs, Rhs rhs)
{
Th rhi = lhs.hi;
Tl rlo = lhs.lo;
Th rhi = lhs.hi_;
Tl rlo = lhs.lo_;
if constexpr (std::is_signed_v<Rhs>) {
// sign extension; no matter if lhs is signed, negative rhs sign extend
if (rhs < 0) ++rhi;
}
rlo -= static_cast<Tl>(rhs);
if (rlo > lhs.lo) {
if (rlo > lhs.lo_) {
// carry bit
--rhi;
}
Expand All @@ -210,39 +229,40 @@ struct double_width_int {
// sign extension; no matter if rhs is signed, negative lhs sign extend
if (lhs < 0) --rhi;
}
rhi -= rhs.hi;
if (rhs.lo > rlo) {
rhi -= rhs.hi_;
if (rhs.lo_ > rlo) {
// carry bit
--rhi;
}
rlo -= rhs.lo;
rlo -= rhs.lo_;
return {rhi, rlo};
}

[[nodiscard]] constexpr double_width_int operator-() const
{
return {(lo > 0 ? static_cast<Th>(-1) : Th{0}) - hi, -lo};
return {(lo_ > 0 ? static_cast<Th>(-1) : Th{0}) - hi_, -lo_};
}

[[nodiscard]] constexpr double_width_int operator>>(unsigned n) const
{
if (n >= base_width) {
return {static_cast<Th>(hi < 0 ? -1 : 0), static_cast<Tl>(hi >> (n - base_width))};
return {static_cast<Th>(hi_ < 0 ? -1 : 0), static_cast<Tl>(hi_ >> (n - base_width))};
}
return {hi >> n, (static_cast<Tl>(hi) << (base_width - n)) | (lo >> n)};
return {hi_ >> n, (static_cast<Tl>(hi_) << (base_width - n)) | (lo_ >> n)};
}
[[nodiscard]] constexpr double_width_int operator<<(unsigned n) const
{
if (n >= base_width) {
return {static_cast<Th>(lo << (n - base_width)), 0};
return {static_cast<Th>(lo_ << (n - base_width)), 0};
}
return {(hi << n) + static_cast<Th>(lo >> (base_width - n)), lo << n};
return {(hi_ << n) + static_cast<Th>(lo_ >> (base_width - n)), lo_ << n};
}

static constexpr double_width_int max() { return {std::numeric_limits<Th>::max(), std::numeric_limits<Tl>::max()}; }

Th hi;
Tl lo;
private:
Th hi_;
Tl lo_;
};

#if defined(__SIZEOF_INT128__)
Expand All @@ -258,8 +278,6 @@ using uint128_t = double_width_int<std::uint64_t>;
constexpr std::size_t max_native_width = 64;
#endif

template<typename T>
constexpr std::size_t integer_rep_width_v = std::numeric_limits<std::make_unsigned_t<T>>::digits;
template<typename T>
constexpr std::size_t integer_rep_width_v<double_width_int<T>> = double_width_int<T>::width;

Expand Down
25 changes: 13 additions & 12 deletions test/runtime/fixed_point_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ auto combine_bits(Hi hi, Lo lo)
template<std::integral T, typename V>
void check(double_width_int<T> value, V&& visitor)
{
auto as_standard_int = combine_bits(value.hi, value.lo);
using DT = double_width_int_t<T>;
auto as_standard_int = static_cast<DT>(value);
auto expected = visitor(as_standard_int);
auto actual = visitor(value);
auto actual_as_standard = combine_bits(actual.hi, actual.lo);
auto actual_as_standard = static_cast<DT>(actual);
REQUIRE(actual_as_standard == expected);
}

Expand Down Expand Up @@ -133,7 +134,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<u32>(), test_values<u32>(), test_values<u32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<u32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v + rhs; });
check(lhs, [&](auto v) { return v - rhs; });
check(lhs, [&](auto v) { return rhs - v; });
Expand All @@ -143,7 +144,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<u32>(), test_values<u32>(), test_values<i32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<u32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v + rhs; });
check(lhs, [&](auto v) { return v - rhs; });
check(lhs, [&](auto v) { return rhs - v; });
Expand All @@ -153,7 +154,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<i32>(), test_values<u32>(), test_values<u32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<i32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v + rhs; });
check(lhs, [&](auto v) { return v - rhs; });
check(lhs, [&](auto v) { return rhs - v; });
Expand All @@ -163,7 +164,7 @@ TEST_CASE("double_width_int addition and subtraction", "[double_width_int]")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<i32>(), test_values<u32>(), test_values<i32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<i32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v + rhs; });
check(lhs, [&](auto v) { return v - rhs; });
check(lhs, [&](auto v) { return rhs - v; });
Expand All @@ -179,7 +180,7 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]")
CAPTURE(lhs, rhs);
u64 expected = u64{lhs} * u64{rhs};
auto actual = double_width_int<u32>::wide_product_of(lhs, rhs);
u64 actual_as_std = combine_bits(actual.hi, actual.lo);
auto actual_as_std = static_cast<u64>(actual);
REQUIRE(actual_as_std == expected);
}
}
Expand All @@ -189,39 +190,39 @@ TEST_CASE("double_width_int multiplication", "[double_width_int]")
CAPTURE(lhs, rhs);
i64 expected = i64{lhs} * i64{rhs};
auto actual = double_width_int<i32>::wide_product_of(lhs, rhs);
i64 actual_as_std = combine_bits(actual.hi, actual.lo);
auto actual_as_std = static_cast<i64>(actual);
REQUIRE(actual_as_std == expected);
}
}
SECTION("u32x2 * u32")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<u32>(), test_values<u32>(), test_values<u32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<u32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v * rhs; });
}
}
SECTION("u32x2 * i32")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<u32>(), test_values<u32>(), test_values<i32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<u32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v * rhs; });
}
}
SECTION("i32x2 * u32")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<i32>(), test_values<u32>(), test_values<u32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<i32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v * rhs; });
}
}
SECTION("i32x2 * i32")
{
for (auto [lhi, llo, rhs] : cartesian_product(test_values<i32>(), test_values<u32>(), test_values<i32>())) {
CAPTURE(lhi, llo, rhs);
double_width_int lhs{lhi, llo};
auto lhs = double_width_int<i32>::from_hi_lo(lhi, llo);
check(lhs, [&](auto v) { return v * rhs; });
}
}
Expand Down

0 comments on commit 5f8eb5c

Please sign in to comment.