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
148 changes: 95 additions & 53 deletions include/bits/bits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <climits>
#include <concepts>
#include <limits>
#include <type_traits>

namespace org::ttldtor::bits {
Expand Down Expand Up @@ -46,46 +47,66 @@ using Max = detail::MaxImpl<Ts...>::Type;
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V sar(V value, S shift) noexcept;
constexpr V sar(V value, S shift) noexcept;

/**
* Performs a left arithmetic bit shift operation (<< in Java, C, etc.).
* Performs a left arithmetic bit shift operation (<< in Java, C, etc.). The `shift` is unsigned.
*
* The result of the shift will be of the same type as the `value` being shifted.
* If the shift is a negative number of bits, then a @ref ::sar() "right arithmetic shift" will be performed.
* If the shift size is greater than or equal to the number of bits in the shifted `value`, then `0` will be
* returned.
*
* @tparam V The type of `value`
* @tparam S The type of `shift`
* @tparam US The type of `shift`
* @param value The value to be shifted
* @param shift The shift in bits
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V leftArithmeticShift(V value, S shift) noexcept {
using UnsignedShift = std::make_unsigned_t<S>;

if constexpr (std::is_signed_v<S>) {
if (shift < 0) {
const auto magnitude = UnsignedShift{} - static_cast<UnsignedShift>(shift);
template <std::integral V, std::unsigned_integral US>
constexpr V leftArithmeticShift(V value, US shift) noexcept {
using UV = std::make_unsigned_t<V>;
constexpr US width = static_cast<US>(std::numeric_limits<UV>::digits);

return sar(value, magnitude);
}
if (shift >= width) {
return V{0};
}

if (shift == 0 || value == 0) {
return value;
return static_cast<V>(static_cast<UV>(value) << shift);
}

/**
* Performs a left arithmetic bit shift operation (<< in Java, C, etc.). The `shift` is signed.
*
* The result of the shift will be of the same type as the `value` being shifted.
* If the shift is a negative number of bits, then a @ref ::sar() "right arithmetic shift" will be performed.
* If the shift size is greater than or equal to the number of bits in the shifted `value`, then `0` will be
* returned.
*
* @tparam V The type of `value`
* @tparam SS The type of `shift`
* @param value The value to be shifted
* @param shift The shift in bits
* @return The shifted `value`
*/
template <std::integral V, std::signed_integral SS>
constexpr V leftArithmeticShift(V value, SS shift) noexcept {
using US = std::make_unsigned_t<SS>;

if (shift < 0) {
const US magnitude = US{0} - static_cast<US>(shift);

return sar(value, magnitude);
}

const auto unsignedShift = static_cast<UnsignedShift>(shift);
const auto bitWidth = static_cast<UnsignedShift>(sizeof(V) * CHAR_BIT);
using UV = std::make_unsigned_t<V>;
constexpr US width = static_cast<US>(std::numeric_limits<UV>::digits);
const US unsignedShift = static_cast<US>(shift);

if (unsignedShift >= bitWidth) {
if (unsignedShift >= width) {
return V{0};
}

return static_cast<V>(value << unsignedShift);
return static_cast<V>(static_cast<UV>(value) << unsignedShift);
}

/**
Expand All @@ -103,45 +124,66 @@ static constexpr V leftArithmeticShift(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V sal(V value, S shift) noexcept {
constexpr V sal(V value, S shift) noexcept {
return leftArithmeticShift(value, shift);
}

/**
* Performs a right arithmetic bit shift operation (>> in Java, C, etc.). The sign bit is extended to preserve the
* signedness of the number.
* Performs a right arithmetic bit shift operation (>> in Java, C, etc.). The `shift` is unsigned.
* The sign bit is extended to preserve the signedness of the number.
*
* The result of the shift will be of the same type as the `value` being shifted.
* If the shift is a negative number of bits, then a @ref ::sal() "left arithmetic shift" will be performed.
* If the shift size is greater than or equal to the number of bits in the shifted `value`, then, if the `value` is
* negative (a signed integer type), `-1` will be returned, and if positive, then `0` will be returned.
*
* @tparam V The type of `value`
* @tparam S The type of `shift`
* @tparam US The type of `shift`
* @param value The value to be shifted
* @param shift The shift in bits
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V rightArithmeticShift(V value, S shift) noexcept {
using UnsignedShift = std::make_unsigned_t<S>;

if constexpr (std::is_signed_v<S>) {
if (shift < 0) {
const UnsignedShift magnitude = UnsignedShift{} - static_cast<UnsignedShift>(shift);
template <std::integral V, std::unsigned_integral US>
constexpr V rightArithmeticShift(V value, US shift) noexcept {
using UV = std::make_unsigned_t<V>;
constexpr US width = static_cast<US>(std::numeric_limits<UV>::digits);

return sal(value, magnitude);
}
if (shift >= width) {
return value < 0 ? static_cast<V>(-1) : V{0};
}

if (shift == 0 || value == 0) {
return value;
return static_cast<V>(value >> shift);
}

/**
* Performs a right arithmetic bit shift operation (>> in Java, C, etc.). The `shift` is signed.
* The sign bit is extended to preserve the signedness of the number.
*
* The result of the shift will be of the same type as the `value` being shifted.
* If the shift is a negative number of bits, then a @ref ::sal() "left arithmetic shift" will be performed.
* If the shift size is greater than or equal to the number of bits in the shifted `value`, then, if the `value` is
* negative (a signed integer type), `-1` will be returned, and if positive, then `0` will be returned.
*
* @tparam V The type of `value`
* @tparam SS The type of `shift`
* @param value The value to be shifted
* @param shift The shift in bits
* @return The shifted `value`
*/
template <std::integral V, std::signed_integral SS>
constexpr V rightArithmeticShift(V value, SS shift) noexcept {
using US = std::make_unsigned_t<SS>;

if (shift < 0) {
const US magnitude = US{0} - static_cast<US>(shift);

return sal(value, magnitude);
}

const auto unsignedShift = static_cast<UnsignedShift>(shift);
const auto bitWidth = static_cast<UnsignedShift>(sizeof(V) * CHAR_BIT);
using UV = std::make_unsigned_t<V>;
constexpr US width = static_cast<US>(std::numeric_limits<UV>::digits);
const US unsignedShift = static_cast<US>(shift);

if (unsignedShift >= bitWidth) {
if (unsignedShift >= width) {
return value < 0 ? static_cast<V>(-1) : V{0};
}

Expand All @@ -164,7 +206,7 @@ static constexpr V rightArithmeticShift(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V sar(V value, S shift) noexcept {
constexpr V sar(V value, S shift) noexcept {
return rightArithmeticShift(value, shift);
}

Expand All @@ -183,7 +225,7 @@ static constexpr V sar(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V shr(V value, S shift) noexcept;
constexpr V shr(V value, S shift) noexcept;

/**
* Performs a left logical bit shift operation (shl).
Expand All @@ -200,7 +242,7 @@ static constexpr V shr(V value, S shift) noexcept;
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V leftLogicalShift(V value, S shift) noexcept {
constexpr V leftLogicalShift(V value, S shift) noexcept {
using UnsignedShift = std::make_unsigned_t<S>;

if constexpr (std::is_signed_v<S>) {
Expand Down Expand Up @@ -240,7 +282,7 @@ static constexpr V leftLogicalShift(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V shl(V value, S shift) noexcept {
constexpr V shl(V value, S shift) noexcept {
return leftLogicalShift(value, shift);
}

Expand All @@ -259,7 +301,7 @@ static constexpr V shl(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V rightLogicalShift(V value, S shift) noexcept {
constexpr V rightLogicalShift(V value, S shift) noexcept {
using UnsignedValue = std::make_unsigned_t<V>;
using UnsignedShift = std::make_unsigned_t<S>;

Expand Down Expand Up @@ -300,7 +342,7 @@ static constexpr V rightLogicalShift(V value, S shift) noexcept {
* @return The shifted `value`
*/
template <std::integral V, std::integral S>
static constexpr V shr(V value, S shift) noexcept {
constexpr V shr(V value, S shift) noexcept {
return rightLogicalShift(value, shift);
}

Expand All @@ -315,7 +357,7 @@ static constexpr V shr(V value, S shift) noexcept {
* @return The result of the bitwise AND operation, cast to type A.
*/
template <std::integral A, std::integral B>
static constexpr A andOp(A a, B b) noexcept {
constexpr A andOp(A a, B b) noexcept {
using Common = std::make_unsigned_t<Max<A, B>>;

return static_cast<A>(static_cast<Common>(a) & static_cast<Common>(b));
Expand All @@ -332,7 +374,7 @@ static constexpr A andOp(A a, B b) noexcept {
* @return The result of the bitwise OR operation, cast to type A.
*/
template <std::integral A, std::integral B>
static constexpr A orOp(A a, B b) noexcept {
constexpr A orOp(A a, B b) noexcept {
using Common = std::make_unsigned_t<Max<A, B>>;

return static_cast<A>(static_cast<Common>(a) | static_cast<Common>(b));
Expand All @@ -349,7 +391,7 @@ static constexpr A orOp(A a, B b) noexcept {
* @return The result of the bitwise XOR operation, cast to type A.
*/
template <std::integral A, std::integral B>
static constexpr A xorOp(A a, B b) noexcept {
constexpr A xorOp(A a, B b) noexcept {
using Common = std::make_unsigned_t<Max<A, B>>;

return static_cast<A>(static_cast<Common>(a) ^ static_cast<Common>(b));
Expand All @@ -364,7 +406,7 @@ static constexpr A xorOp(A a, B b) noexcept {
* @return `true` if the specified bits are set in the source value.
*/
template <std::unsigned_integral T>
static constexpr T bitsAreSet(T sourceBits, T bitMaskToCheck) {
constexpr T bitsAreSet(T sourceBits, T bitMaskToCheck) {
return andOp(sourceBits, bitMaskToCheck) != 0;
}

Expand All @@ -378,7 +420,7 @@ static constexpr T bitsAreSet(T sourceBits, T bitMaskToCheck) {
* @return `true` if the specified bits are set in the source value.
*/
template <std::integral SB, std::integral M>
static constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) {
constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) {
using MaxType = Max<SB, M>;

if constexpr (std::is_signed_v<SB> || std::is_signed_v<M>) {
Expand All @@ -399,7 +441,7 @@ static constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) {
* @return The resulting value after setting the specified bits.
*/
template <std::unsigned_integral T>
static constexpr T setBits(T sourceBits, T bitMaskToSet) {
constexpr T setBits(T sourceBits, T bitMaskToSet) {
return orOp(sourceBits, bitMaskToSet);
}

Expand All @@ -413,7 +455,7 @@ static constexpr T setBits(T sourceBits, T bitMaskToSet) {
* @return The resulting value after setting the specified bits.
*/
template <std::integral SB, std::integral M>
static constexpr SB setBits(SB sourceBits, M bitMaskToSet) {
constexpr SB setBits(SB sourceBits, M bitMaskToSet) {
using MaxType = Max<SB, M>;

if constexpr (std::is_signed_v<SB> || std::is_signed_v<M>) {
Expand All @@ -434,7 +476,7 @@ static constexpr SB setBits(SB sourceBits, M bitMaskToSet) {
* @return The resulting value after resetting the specified bits.
*/
template <std::unsigned_integral T>
static constexpr T resetBits(T sourceBits, T bitMaskToReset) {
constexpr T resetBits(T sourceBits, T bitMaskToReset) {
return andOp(sourceBits, ~bitMaskToReset);
}

Expand All @@ -449,7 +491,7 @@ static constexpr T resetBits(T sourceBits, T bitMaskToReset) {
*/
template <std::integral SB, std::integral M>
// ReSharper disable once CppDFAConstantParameter
static constexpr SB resetBits(SB sourceBits, M bitMaskToReset) {
constexpr SB resetBits(SB sourceBits, M bitMaskToReset) {
using MaxType = Max<SB, M>;

if constexpr (std::is_signed_v<SB> || std::is_signed_v<M>) {
Expand Down
6 changes: 5 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ if (CMAKE_CONFIGURATION_TYPES)
add_test(NAME bits_bench COMMAND bits_bench)
set_tests_properties(bits_bench PROPERTIES DISABLED "$<CONFIG:Debug>")
else ()
if (CMAKE_BUILD_TYPE STREQUAL "Release")
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
add_test(NAME bits_bench COMMAND bits_bench)
else ()
set_property(TARGET bits_bench PROPERTY EXCLUDE_FROM_ALL TRUE)
endif ()
endif ()

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(bits_bench PRIVATE -O3 -march=native -Werror -pedantic)
endif ()
3 changes: 0 additions & 3 deletions tests/bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
using namespace org::ttldtor::bits;
using namespace std::literals;

// NOLINTNEXTLINE

TEST_CASE("bench_sal_sar_vs_builtin") {
constexpr size_t N = 1u << 15;

Expand Down Expand Up @@ -85,7 +83,6 @@ TEST_CASE("bench_sal_sar_vs_builtin") {
});
};


auto runSarBench = [&](auto& bench, const auto& typeName, const auto& values, const auto& shifts) {
bench.run("builtin >> ("s + typeName + ")", [&] {
uint32_t acc = 0;
Expand Down
Loading