Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 350 additions & 0 deletions include/boost/safe_numbers/detail/signed_integer_basis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class signed_integer_basis

template <fundamental_signed_integral OtherBasis>
constexpr auto operator+=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;

template <fundamental_signed_integral OtherBasis>
constexpr auto operator-=(signed_integer_basis<OtherBasis> rhs) -> signed_integer_basis&;
};

// Helper for diagnostic messages
Expand Down Expand Up @@ -772,6 +775,353 @@ constexpr auto signed_integer_basis<BasisType>::operator+=(const signed_integer_
return *this;
}

// ------------------------------
// Subtraction
// ------------------------------

namespace impl {

template <fundamental_signed_integral BasisType>
constexpr auto signed_overflow_sub_msg() noexcept -> const char*
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
return "Overflow detected in i8 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
return "Overflow detected in i16 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
return "Overflow detected in i32 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
return "Overflow detected in i64 subtraction";
}
else
{
return "Overflow detected in i128 subtraction";
}
}

template <fundamental_signed_integral BasisType>
constexpr auto signed_underflow_sub_msg() noexcept -> const char*
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
return "Underflow detected in i8 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
return "Underflow detected in i16 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
return "Underflow detected in i32 subtraction";
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
return "Underflow detected in i64 subtraction";
}
else
{
return "Underflow detected in i128 subtraction";
}
}

#if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow)

template <std::signed_integral T>
auto signed_intrin_sub(const T lhs, const T rhs, T& result) -> signed_overflow_status
{
if (__builtin_sub_overflow(lhs, rhs, &result))
{
return classify_signed_overflow(lhs);
}

return signed_overflow_status::no_error;
}

#elif defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN)

template <std::signed_integral T>
auto signed_intrin_sub(const T lhs, const T rhs, T& result) -> signed_overflow_status
{
using unsigned_t = std::make_unsigned_t<T>;

unsigned_t temp {};

if constexpr (std::is_same_v<T, std::int8_t>)
{
_subborrow_u8(0, static_cast<std::uint8_t>(lhs), static_cast<std::uint8_t>(rhs), &temp);
}
else if constexpr (std::is_same_v<T, std::int16_t>)
{
_subborrow_u16(0, static_cast<std::uint16_t>(lhs), static_cast<std::uint16_t>(rhs), &temp);
}
else if constexpr (std::is_same_v<T, std::int32_t>)
{
_subborrow_u32(0, static_cast<std::uint32_t>(lhs), static_cast<std::uint32_t>(rhs), &temp);
}
#if defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN)
else if constexpr (std::is_same_v<T, std::int64_t>)
{
// x86 does not provide the _subborrow_u64 intrinsic
temp = static_cast<std::uint64_t>(lhs) - static_cast<std::uint64_t>(rhs);
}
#else
else if constexpr (std::is_same_v<T, std::int64_t>)
{
_subborrow_u64(0, static_cast<std::uint64_t>(lhs), static_cast<std::uint64_t>(rhs), &temp);
}
#endif

result = static_cast<T>(temp);

// Signed subtraction overflow: operands have different signs and result sign differs from lhs
const auto ulhs {static_cast<unsigned_t>(lhs)};
const auto urhs {static_cast<unsigned_t>(rhs)};
const auto has_overflow {static_cast<bool>(((ulhs ^ urhs) & (ulhs ^ temp)) >> std::numeric_limits<T>::digits)};

if (has_overflow)
{
return classify_signed_overflow(lhs);
}

return signed_overflow_status::no_error;
}

#endif

template <fundamental_signed_integral T>
constexpr auto signed_no_intrin_sub(const T lhs, const T rhs, T& result) noexcept -> signed_overflow_status
{
using unsigned_t = make_unsigned_helper_t<T>;
unsigned_t temp {};

if constexpr (std::is_same_v<T, std::int8_t> || std::is_same_v<T, std::int16_t>)
{
temp = static_cast<unsigned_t>(static_cast<std::uint32_t>(lhs) - static_cast<std::uint32_t>(rhs));
}
else
{
temp = static_cast<unsigned_t>(lhs) - static_cast<unsigned_t>(rhs);
}

result = static_cast<T>(temp);

// Signed subtraction overflow: operands have different signs and result sign differs from lhs
const auto ulhs {static_cast<unsigned_t>(lhs)};
const auto urhs {static_cast<unsigned_t>(rhs)};
const auto has_overflow {static_cast<bool>(((ulhs ^ urhs) & (ulhs ^ temp)) >> std::numeric_limits<T>::digits)};

if (has_overflow)
{
return classify_signed_overflow(lhs);
}

return signed_overflow_status::no_error;
}

template <overflow_policy Policy, fundamental_signed_integral BasisType>
struct signed_sub_helper
{
[[nodiscard]] static constexpr auto apply(const signed_integer_basis<BasisType> lhs,
const signed_integer_basis<BasisType> rhs)
noexcept(Policy != overflow_policy::throw_exception)
-> signed_integer_basis<BasisType>
{
using result_type = signed_integer_basis<BasisType>;

const auto lhs_basis {static_cast<BasisType>(lhs)};
const auto rhs_basis {static_cast<BasisType>(rhs)};
BasisType result {};

auto handle_error = [&result](signed_overflow_status status)
{
if (std::is_constant_evaluated())
{
if (status == signed_overflow_status::overflow)
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
throw std::overflow_error("Overflow detected in i8 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
throw std::overflow_error("Overflow detected in i16 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
throw std::overflow_error("Overflow detected in i32 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
throw std::overflow_error("Overflow detected in i64 subtraction");
}
else
{
throw std::overflow_error("Overflow detected in i128 subtraction");
}
}
else
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
throw std::underflow_error("Underflow detected in i8 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
throw std::underflow_error("Underflow detected in i16 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
throw std::underflow_error("Underflow detected in i32 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
throw std::underflow_error("Underflow detected in i64 subtraction");
}
else
{
throw std::underflow_error("Underflow detected in i128 subtraction");
}
}
}
else
{
if constexpr (Policy == overflow_policy::throw_exception)
{
static_cast<void>(result);

if (status == signed_overflow_status::overflow)
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::overflow_error, signed_overflow_sub_msg<BasisType>());
}
else
{
BOOST_SAFE_NUMBERS_THROW_EXCEPTION(std::underflow_error, signed_underflow_sub_msg<BasisType>());
}
}
else
{
static_cast<void>(result);
BOOST_SAFE_NUMBERS_UNREACHABLE;
}
}
};

#if BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN)

if constexpr (!std::is_same_v<BasisType, int128::int128_t>)
{
if (!std::is_constant_evaluated())
{
const auto status {impl::signed_intrin_sub(lhs_basis, rhs_basis, result)};
if (status != signed_overflow_status::no_error)
{
handle_error(status);
}

return result_type{result};
}
}

#endif // BOOST_SAFE_NUMBERS_HAS_BUILTIN(__builtin_sub_overflow) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X64_INTRIN) || defined(BOOST_SAFENUMBERS_HAS_WINDOWS_X86_INTRIN)

const auto status {impl::signed_no_intrin_sub(lhs_basis, rhs_basis, result)};
if (status != signed_overflow_status::no_error)
{
handle_error(status);
}

return result_type{result};
}
};

template <overflow_policy Policy, fundamental_signed_integral BasisType>
[[nodiscard]] constexpr auto sub_impl(const signed_integer_basis<BasisType> lhs,
const signed_integer_basis<BasisType> rhs)
{
return signed_sub_helper<Policy, BasisType>::apply(lhs, rhs);
}

} // namespace impl

template <fundamental_signed_integral BasisType>
[[nodiscard]] constexpr auto operator-(const signed_integer_basis<BasisType> lhs,
const signed_integer_basis<BasisType> rhs) -> signed_integer_basis<BasisType>
{
if (std::is_constant_evaluated())
{
BasisType res {};
const auto status {impl::signed_no_intrin_sub(static_cast<BasisType>(lhs), static_cast<BasisType>(rhs), res)};
if (status == impl::signed_overflow_status::overflow)
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
throw std::overflow_error("Overflow detected in i8 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
throw std::overflow_error("Overflow detected in i16 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
throw std::overflow_error("Overflow detected in i32 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
throw std::overflow_error("Overflow detected in i64 subtraction");
}
else
{
throw std::overflow_error("Overflow detected in i128 subtraction");
}
}
else if (status == impl::signed_overflow_status::underflow)
{
if constexpr (std::is_same_v<BasisType, std::int8_t>)
{
throw std::underflow_error("Underflow detected in i8 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int16_t>)
{
throw std::underflow_error("Underflow detected in i16 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int32_t>)
{
throw std::underflow_error("Underflow detected in i32 subtraction");
}
else if constexpr (std::is_same_v<BasisType, std::int64_t>)
{
throw std::underflow_error("Underflow detected in i64 subtraction");
}
else
{
throw std::underflow_error("Underflow detected in i128 subtraction");
}
}

return signed_integer_basis<BasisType>{res};
}

return impl::signed_sub_helper<overflow_policy::throw_exception, BasisType>::apply(lhs, rhs);
}

BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP("subtraction", operator-)

template <fundamental_signed_integral BasisType>
template <fundamental_signed_integral OtherBasisType>
constexpr auto signed_integer_basis<BasisType>::operator-=(const signed_integer_basis<OtherBasisType> rhs)
-> signed_integer_basis&
{
*this = *this - rhs;
return *this;
}

} // namespace boost::safe_numbers::detail

#undef BOOST_SAFE_NUMBERS_DEFINE_MIXED_SIGNED_INTEGER_OP
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ run test_signed_conversions.cpp ;
run test_signed_comparisons.cpp ;
run test_signed_unary_operators.cpp ;
run test_signed_addition.cpp ;
run test_signed_subtraction.cpp ;

run test_unsigned_addition.cpp ;
compile-fail compile_fail_unsigned_addition.cpp ;
Expand Down
Loading
Loading