diff --git a/include/bits/bits.hpp b/include/bits/bits.hpp index b68ad3d..eda759c 100644 --- a/include/bits/bits.hpp +++ b/include/bits/bits.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace org::ttldtor::bits { @@ -46,46 +47,66 @@ using Max = detail::MaxImpl::Type; * @return The shifted `value` */ template -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 -static constexpr V leftArithmeticShift(V value, S shift) noexcept { - using UnsignedShift = std::make_unsigned_t; - - if constexpr (std::is_signed_v) { - if (shift < 0) { - const auto magnitude = UnsignedShift{} - static_cast(shift); +template +constexpr V leftArithmeticShift(V value, US shift) noexcept { + using UV = std::make_unsigned_t; + constexpr US width = static_cast(std::numeric_limits::digits); - return sar(value, magnitude); - } + if (shift >= width) { + return V{0}; } - if (shift == 0 || value == 0) { - return value; + return static_cast(static_cast(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 +constexpr V leftArithmeticShift(V value, SS shift) noexcept { + using US = std::make_unsigned_t; + + if (shift < 0) { + const US magnitude = US{0} - static_cast(shift); + + return sar(value, magnitude); } - const auto unsignedShift = static_cast(shift); - const auto bitWidth = static_cast(sizeof(V) * CHAR_BIT); + using UV = std::make_unsigned_t; + constexpr US width = static_cast(std::numeric_limits::digits); + const US unsignedShift = static_cast(shift); - if (unsignedShift >= bitWidth) { + if (unsignedShift >= width) { return V{0}; } - return static_cast(value << unsignedShift); + return static_cast(static_cast(value) << unsignedShift); } /** @@ -103,45 +124,66 @@ static constexpr V leftArithmeticShift(V value, S shift) noexcept { * @return The shifted `value` */ template -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 -static constexpr V rightArithmeticShift(V value, S shift) noexcept { - using UnsignedShift = std::make_unsigned_t; - - if constexpr (std::is_signed_v) { - if (shift < 0) { - const UnsignedShift magnitude = UnsignedShift{} - static_cast(shift); +template +constexpr V rightArithmeticShift(V value, US shift) noexcept { + using UV = std::make_unsigned_t; + constexpr US width = static_cast(std::numeric_limits::digits); - return sal(value, magnitude); - } + if (shift >= width) { + return value < 0 ? static_cast(-1) : V{0}; } - if (shift == 0 || value == 0) { - return value; + return static_cast(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 +constexpr V rightArithmeticShift(V value, SS shift) noexcept { + using US = std::make_unsigned_t; + + if (shift < 0) { + const US magnitude = US{0} - static_cast(shift); + + return sal(value, magnitude); } - const auto unsignedShift = static_cast(shift); - const auto bitWidth = static_cast(sizeof(V) * CHAR_BIT); + using UV = std::make_unsigned_t; + constexpr US width = static_cast(std::numeric_limits::digits); + const US unsignedShift = static_cast(shift); - if (unsignedShift >= bitWidth) { + if (unsignedShift >= width) { return value < 0 ? static_cast(-1) : V{0}; } @@ -164,7 +206,7 @@ static constexpr V rightArithmeticShift(V value, S shift) noexcept { * @return The shifted `value` */ template -static constexpr V sar(V value, S shift) noexcept { +constexpr V sar(V value, S shift) noexcept { return rightArithmeticShift(value, shift); } @@ -183,7 +225,7 @@ static constexpr V sar(V value, S shift) noexcept { * @return The shifted `value` */ template -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). @@ -200,7 +242,7 @@ static constexpr V shr(V value, S shift) noexcept; * @return The shifted `value` */ template -static constexpr V leftLogicalShift(V value, S shift) noexcept { +constexpr V leftLogicalShift(V value, S shift) noexcept { using UnsignedShift = std::make_unsigned_t; if constexpr (std::is_signed_v) { @@ -240,7 +282,7 @@ static constexpr V leftLogicalShift(V value, S shift) noexcept { * @return The shifted `value` */ template -static constexpr V shl(V value, S shift) noexcept { +constexpr V shl(V value, S shift) noexcept { return leftLogicalShift(value, shift); } @@ -259,7 +301,7 @@ static constexpr V shl(V value, S shift) noexcept { * @return The shifted `value` */ template -static constexpr V rightLogicalShift(V value, S shift) noexcept { +constexpr V rightLogicalShift(V value, S shift) noexcept { using UnsignedValue = std::make_unsigned_t; using UnsignedShift = std::make_unsigned_t; @@ -300,7 +342,7 @@ static constexpr V rightLogicalShift(V value, S shift) noexcept { * @return The shifted `value` */ template -static constexpr V shr(V value, S shift) noexcept { +constexpr V shr(V value, S shift) noexcept { return rightLogicalShift(value, shift); } @@ -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 -static constexpr A andOp(A a, B b) noexcept { +constexpr A andOp(A a, B b) noexcept { using Common = std::make_unsigned_t>; return static_cast(static_cast(a) & static_cast(b)); @@ -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 -static constexpr A orOp(A a, B b) noexcept { +constexpr A orOp(A a, B b) noexcept { using Common = std::make_unsigned_t>; return static_cast(static_cast(a) | static_cast(b)); @@ -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 -static constexpr A xorOp(A a, B b) noexcept { +constexpr A xorOp(A a, B b) noexcept { using Common = std::make_unsigned_t>; return static_cast(static_cast(a) ^ static_cast(b)); @@ -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 -static constexpr T bitsAreSet(T sourceBits, T bitMaskToCheck) { +constexpr T bitsAreSet(T sourceBits, T bitMaskToCheck) { return andOp(sourceBits, bitMaskToCheck) != 0; } @@ -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 -static constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) { +constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) { using MaxType = Max; if constexpr (std::is_signed_v || std::is_signed_v) { @@ -399,7 +441,7 @@ static constexpr SB bitsAreSet(SB sourceBits, M bitMaskToCheck) { * @return The resulting value after setting the specified bits. */ template -static constexpr T setBits(T sourceBits, T bitMaskToSet) { +constexpr T setBits(T sourceBits, T bitMaskToSet) { return orOp(sourceBits, bitMaskToSet); } @@ -413,7 +455,7 @@ static constexpr T setBits(T sourceBits, T bitMaskToSet) { * @return The resulting value after setting the specified bits. */ template -static constexpr SB setBits(SB sourceBits, M bitMaskToSet) { +constexpr SB setBits(SB sourceBits, M bitMaskToSet) { using MaxType = Max; if constexpr (std::is_signed_v || std::is_signed_v) { @@ -434,7 +476,7 @@ static constexpr SB setBits(SB sourceBits, M bitMaskToSet) { * @return The resulting value after resetting the specified bits. */ template -static constexpr T resetBits(T sourceBits, T bitMaskToReset) { +constexpr T resetBits(T sourceBits, T bitMaskToReset) { return andOp(sourceBits, ~bitMaskToReset); } @@ -449,7 +491,7 @@ static constexpr T resetBits(T sourceBits, T bitMaskToReset) { */ template // ReSharper disable once CppDFAConstantParameter -static constexpr SB resetBits(SB sourceBits, M bitMaskToReset) { +constexpr SB resetBits(SB sourceBits, M bitMaskToReset) { using MaxType = Max; if constexpr (std::is_signed_v || std::is_signed_v) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b69fa8b..4f6138d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,9 +48,13 @@ if (CMAKE_CONFIGURATION_TYPES) add_test(NAME bits_bench COMMAND bits_bench) set_tests_properties(bits_bench PROPERTIES DISABLED "$") 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 () diff --git a/tests/bench.cpp b/tests/bench.cpp index ac73606..182a5ba 100644 --- a/tests/bench.cpp +++ b/tests/bench.cpp @@ -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; @@ -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;