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
34 changes: 34 additions & 0 deletions doc/modules/ROOT/pages/examples.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,40 @@ byteswap(0x12345678) = 0x78563412
----
====

[#examples_bitwise_ops]
== Bitwise Operations

The library provides bitwise operators (`~`, `&`, `|`, `^`, `<<`, `>>`) and their compound assignment forms (`&=`, `|=`, `^=`, `<<=`, `>>=`).
The NOT, AND, OR, and XOR operators are `noexcept`, while the shift operators throw `std::overflow_error` if bits would be shifted past the type width.

.This https://github.com/boostorg/safe_numbers/blob/develop/examples/bitwise_ops.cpp[example] demonstrates the bitwise operators and shift overflow detection.
====
[source, c++]
----
include::example$bitwise_ops.cpp[]
----

Output:
----
~a = 0xff00ff
a & b = 0xf000f00
a | b = 0xff0fff0f
a ^ b = 0xf00ff00f

u8(1) << 4 = 16
u8(128) >> 4 = 8

x &= 0x0F0F -> 0xf00
x |= 0xF000 -> 0xff00
x ^= 0x00FF -> 0xffff
y <<= 8 -> 256
y >>= 4 -> 16

Left shift error: Left shift past the end of the type width
Right shift error: Right shift past the end of the type width
----
====

[#examples_policy_comparison]
== Policy Comparison

Expand Down
95 changes: 94 additions & 1 deletion doc/modules/ROOT/pages/unsigned_integers.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public:
friend constexpr auto operator<=>(unsigned_integer_basis lhs, unsigned_integer_basis rhs) noexcept
-> std::strong_ordering = default;

// Compound assignment operators
// Compound assignment operators (arithmetic)
template <unsigned_integral OtherBasis>
constexpr auto operator+=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

Expand All @@ -75,6 +75,13 @@ public:
template <unsigned_integral OtherBasis>
constexpr auto operator%=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_integer_basis&;

// Compound assignment operators (bitwise)
constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator|=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&;
constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&;

// Increment and decrement operators
constexpr auto operator++() -> unsigned_integer_basis&;
constexpr auto operator++(int) -> unsigned_integer_basis;
Expand Down Expand Up @@ -104,6 +111,36 @@ template <unsigned_integral BasisType>
constexpr auto operator%(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) -> unsigned_integer_basis<BasisType>;

// Bitwise operators
template <unsigned_integral BasisType>
constexpr auto operator~(unsigned_integer_basis<BasisType> lhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator&(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator|(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator^(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator<<(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs)
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator>>(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs)
-> unsigned_integer_basis<BasisType>;

// Saturating arithmetic (clamp to min/max on overflow/underflow)
template <UnsignedLibType T>
constexpr T saturating_add(T lhs, T rhs) noexcept;
Expand Down Expand Up @@ -313,6 +350,62 @@ constexpr auto operator%=(unsigned_integer_basis<OtherBasis> rhs) -> unsigned_in

Compound assignment operators follow the same exception behavior as their corresponding arithmetic operators.

=== Bitwise Operators

[source,c++]
----
template <unsigned_integral BasisType>
constexpr auto operator~(unsigned_integer_basis<BasisType> lhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator&(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator|(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator^(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs) noexcept
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator<<(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs)
-> unsigned_integer_basis<BasisType>;

template <unsigned_integral BasisType>
constexpr auto operator>>(unsigned_integer_basis<BasisType> lhs,
unsigned_integer_basis<BasisType> rhs)
-> unsigned_integer_basis<BasisType>;
----

The bitwise NOT, AND, OR, and XOR operators (`~`, `&`, `|`, `^`) are `noexcept` and operate directly on the underlying values, since these operations cannot overflow.

The shift operators (`<<`, `>>`) perform runtime bounds checking:

- `<<`: Throws `std::overflow_error` if the left shift would move bits past the type width. Specifically, this occurs when `bit_width(lhs) + rhs >= std::numeric_limits<BasisType>::digits`.
- `>>`: Throws `std::overflow_error` if the shift amount is greater than or equal to the type width (i.e., `rhs >= std::numeric_limits<BasisType>::digits`).

=== Compound Bitwise Assignment Operators

[source,c++]
----
constexpr auto operator&=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator|=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator^=(unsigned_integer_basis rhs) noexcept -> unsigned_integer_basis&;
constexpr auto operator<<=(unsigned_integer_basis rhs) -> unsigned_integer_basis&;
constexpr auto operator>>=(unsigned_integer_basis rhs) -> unsigned_integer_basis&;
----

Compound bitwise assignment operators delegate to the corresponding free-function bitwise operators and follow the same exception behavior.
`&=`, `|=`, and `^=` are `noexcept`.
`<<=` and `>>=` throw `std::overflow_error` under the same conditions as `<<` and `>>`.

=== Increment and Decrement Operators

[source,c++]
Expand Down
88 changes: 88 additions & 0 deletions examples/bitwise_ops.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2026 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/safe_numbers/unsigned_integers.hpp>
#include <boost/safe_numbers/iostream.hpp>
#include <iostream>

int main()
{
using namespace boost::safe_numbers;

const auto a = u32{0xFF00FF00u};
const auto b = u32{0x0F0F0F0Fu};

// Bitwise NOT
std::cout << std::hex;
std::cout << "~a = 0x" << ~a << '\n';

// Bitwise AND
std::cout << "a & b = 0x" << (a & b) << '\n';

// Bitwise OR
std::cout << "a | b = 0x" << (a | b) << '\n';

// Bitwise XOR
std::cout << "a ^ b = 0x" << (a ^ b) << '\n';

std::cout << std::dec << '\n';

// Left shift (throws if bits would be shifted past the type width)
const auto one = u8{1};
const auto shift = u8{4};
std::cout << "u8(1) << 4 = " << static_cast<unsigned>(one << shift) << '\n';

// Right shift (throws if the shift amount >= type width)
const auto val = u8{0x80};
std::cout << "u8(128) >> 4 = " << static_cast<unsigned>(val >> shift) << '\n';

std::cout << '\n';

// Compound assignment operators
auto x = u32{0xFF00u};
x &= u32{0x0F0Fu};
std::cout << std::hex;
std::cout << "x &= 0x0F0F -> 0x" << x << '\n';

x |= u32{0xF000u};
std::cout << "x |= 0xF000 -> 0x" << x << '\n';

x ^= u32{0x00FFu};
std::cout << "x ^= 0x00FF -> 0x" << x << '\n';

std::cout << std::dec;
auto y = u32{1};
y <<= u32{8};
std::cout << "y <<= 8 -> " << y << '\n';

y >>= u32{4};
std::cout << "y >>= 4 -> " << y << '\n';

std::cout << '\n';

// Shift overflow detection
try
{
const auto big = u8{0xFF};
const auto result = big << u8{1}; // Would shift bits past width
std::cout << "Should not reach here: " << static_cast<unsigned>(result) << '\n';
}
catch (const std::overflow_error& e)
{
std::cerr << "Left shift error: " << e.what() << '\n';
}

try
{
const auto val2 = u8{1};
const auto result = val2 >> u8{8}; // Shift amount >= type width
std::cout << "Should not reach here: " << static_cast<unsigned>(result) << '\n';
}
catch (const std::overflow_error& e)
{
std::cerr << "Right shift error: " << e.what() << '\n';
}

return 0;
}
2 changes: 1 addition & 1 deletion include/boost/safe_numbers/bounded_integers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class bounded_uint
private:

using underlying_type = detail::underlying_type_t<basis_type>;
basis_type basis_ {static_cast<underlying_type>(detail::raw_value(Min))};
basis_type basis_ {};

public:

Expand Down
2 changes: 1 addition & 1 deletion include/boost/safe_numbers/detail/fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ concept valid_bound = !std::is_same_v<T, bool> && (is_unsigned_library_type_v<T>

template <typename T>
requires valid_bound<T>
consteval auto raw_value(T val) noexcept
constexpr auto raw_value(T val) noexcept
{
if constexpr (is_unsigned_library_type_v<T>)
{
Expand Down
12 changes: 6 additions & 6 deletions include/boost/safe_numbers/detail/int128/detail/uint128_imp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
namespace boost {
namespace int128 {

BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT struct
#ifdef BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128
struct
#if defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_INT128) || defined(BOOST_SAFE_NUMBERS_DETAIL_INT128_HAS_MSVC_INT128)
alignas(alignof(detail::builtin_u128))
#else
alignas(16)
Expand Down Expand Up @@ -402,8 +402,8 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT constexpr bool operator==(const uint128_
}
else
{
__m128i a = _mm_load_si128(reinterpret_cast<const __m128i*>(&lhs));
__m128i b = _mm_load_si128(reinterpret_cast<const __m128i*>(&rhs));
__m128i a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&lhs));
__m128i b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&rhs));
__m128i cmp = _mm_cmpeq_epi32(a, b);

return _mm_movemask_ps(_mm_castsi128_ps(cmp)) == 0xF;
Expand Down Expand Up @@ -538,8 +538,8 @@ BOOST_SAFE_NUMBERS_DETAIL_INT128_EXPORT constexpr bool operator!=(const uint128_
}
else
{
__m128i a = _mm_load_si128(reinterpret_cast<const __m128i*>(&lhs));
__m128i b = _mm_load_si128(reinterpret_cast<const __m128i*>(&rhs));
__m128i a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&lhs));
__m128i b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&rhs));
__m128i cmp = _mm_cmpeq_epi32(a, b);

return _mm_movemask_ps(_mm_castsi128_ps(cmp)) != 0xF;
Expand Down
Loading
Loading