diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 38ad8601..daf5b7d8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(dynarmic common/crc32.h common/fp/fpsr.h common/fp/info.h + common/fp/mantissa_util.h common/fp/process_exception.cpp common/fp/process_exception.h common/fp/rounding_mode.h diff --git a/src/common/fp/mantissa_util.h b/src/common/fp/mantissa_util.h new file mode 100644 index 00000000..2551c40c --- /dev/null +++ b/src/common/fp/mantissa_util.h @@ -0,0 +1,48 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2018 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include "common/bit_util.h" +#include "common/common_types.h" + +namespace Dynarmic::FP { + +enum class ResidualError { + Zero, + LessThanHalf, + Half, + GreaterThanHalf, +}; + +template +ResidualError ResidualErrorOnRightShift(MantissaT mantissa, int shift_amount) { + if (shift_amount <= 0 || mantissa == 0) { + return ResidualError::Zero; + } + + if (shift_amount > static_cast(Common::BitSize())) { + return Common::MostSignificantBit(mantissa) ? ResidualError::GreaterThanHalf : ResidualError::LessThanHalf; + } + + const size_t half_bit_position = static_cast(shift_amount - 1); + const MantissaT half = static_cast(1) << half_bit_position; + const MantissaT error_mask = Common::Ones(static_cast(shift_amount)); + const MantissaT error = mantissa & error_mask; + + if (error == 0) { + return ResidualError::Zero; + } + if (error < half) { + return ResidualError::LessThanHalf; + } + if (error == half) { + return ResidualError::Half; + } + return ResidualError::GreaterThanHalf; +} + +} // namespace Dynarmic::FP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 576bf9e6..e48e18c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ add_executable(dynarmic_tests A64/inst_gen.cpp A64/inst_gen.h A64/testenv.h + fp/mantissa_util_tests.cpp fp/unpacked_tests.cpp main.cpp mp.cpp diff --git a/tests/fp/mantissa_util_tests.cpp b/tests/fp/mantissa_util_tests.cpp new file mode 100644 index 00000000..04703e1d --- /dev/null +++ b/tests/fp/mantissa_util_tests.cpp @@ -0,0 +1,63 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2018 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include +#include + +#include + +#include "common/fp/mantissa_util.h" +#include "common/safe_ops.h" +#include "rand_int.h" + +using namespace Dynarmic; +using namespace Dynarmic::FP; + +TEST_CASE("ResidualErrorOnRightShift", "[fp]") { + const std::vector> test_cases { + {0x00000001, 1, ResidualError::Half}, + {0x00000002, 1, ResidualError::Zero}, + {0x00000001, 2, ResidualError::LessThanHalf}, + {0x00000002, 2, ResidualError::Half}, + {0x00000003, 2, ResidualError::GreaterThanHalf}, + {0x00000004, 2, ResidualError::Zero}, + {0x00000005, 2, ResidualError::LessThanHalf}, + {0x00000006, 2, ResidualError::Half}, + {0x00000007, 2, ResidualError::GreaterThanHalf}, + }; + + for (auto [mantissa, shift, expected_result] : test_cases) { + const ResidualError result = ResidualErrorOnRightShift(mantissa, shift); + REQUIRE(result == expected_result); + } +} + +TEST_CASE("ResidualErrorOnRightShift Randomized", "[fp]") { + for (size_t test = 0; test < 100000; test++) { + const u32 mantissa = RandInt(0, 0xFFFFFFFF); + const int shift = RandInt(-60, 60); + + const ResidualError result = ResidualErrorOnRightShift(mantissa, shift); + + const u64 calculated_error = Safe::ArithmeticShiftRightDouble(Common::SignExtend<32, u64>(mantissa), u64(0), shift); + const ResidualError expected_result = [&]{ + constexpr u64 half_error = 0x8000'0000'0000'0000ull; + if (calculated_error == 0) { + return ResidualError::Zero; + } + if (calculated_error < half_error) { + return ResidualError::LessThanHalf; + } + if (calculated_error == half_error) { + return ResidualError::Half; + } + return ResidualError::GreaterThanHalf; + }(); + + INFO(std::hex << "mantissa " << mantissa << " shift " << shift << " calculated_error " << calculated_error); + REQUIRE(result == expected_result); + } +}