1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 06:27:45 +00:00

AK: Fix FixedPoint multiplication rounding and overflow behaviour

We now preform the multiplication in a widened type which makes it
overflow-safe and use the correct bit for rounding direction detection.
This commit is contained in:
Hendiadyoin1 2023-07-23 19:42:19 +02:00 committed by Andrew Kaster
parent 32de3ddd33
commit e609ac74a3
2 changed files with 21 additions and 23 deletions

View file

@ -202,19 +202,24 @@ public:
} }
constexpr This operator*(This const& other) const constexpr This operator*(This const& other) const
{ {
// FIXME: Potential Overflow, although result could be represented accurately // FIXME: Figure out a way to use more narrow types and avoid __int128
Underlying value = m_value * other.raw(); using MulRes = Conditional<sizeof(Underlying) < sizeof(i64), i64, __int128>;
This ret {};
ret.raw() = value >> precision; MulRes value = raw();
// fract(value) >= .5? value *= other.raw();
This ret = create_raw(value >> precision);
// Rounding:
// If last bit cut off is 1:
if (value & (1u << (precision - 1))) { if (value & (1u << (precision - 1))) {
// fract(value) > .5? // If the bit after is 1 as well
if (value & (radix_mask >> 2u)) { if (value & (radix_mask >> 2u)) {
// yes: round up; // We round away from 0
ret.raw() += (value > 0 ? 1 : -1); ret.raw() += 1;
} else { } else {
// no: round to even (aka unset last sigificant bit); // Otherwise we round to the next even value
ret.raw() += m_value & 1; // Which means we add the least significant bit of the raw return value
ret.raw() += ret.raw() & 1;
} }
} }
return ret; return ret;
@ -268,19 +273,7 @@ public:
} }
This& operator*=(This const& other) This& operator*=(This const& other)
{ {
Underlying value = m_value * other.raw(); *this = *this * other;
m_value = value >> precision;
// fract(value) >= .5?
if (value & (1u << (precision - 1))) {
// fract(value) > .5?
if (value & (radix_mask >> 2u)) {
// yes: round up;
m_value += (value > 0 ? 1 : -1);
} else {
// no: round to even (aka unset last sigificant bit);
m_value += m_value & 1;
}
}
return *this; return *this;
} }
This& operator/=(This const& other) This& operator/=(This const& other)

View file

@ -26,6 +26,11 @@ TEST_CASE(arithmetic)
EXPECT_EQ( EXPECT_EQ(
Type((int)1) * Type(0.5), Type((int)1) * Type(0.5),
Type(0.5)); Type(0.5));
EXPECT_EQ(Type(0.125) * Type(3.75),
Type(0.125 * 3.75));
EXPECT_EQ(Type(0.125) * Type(-3.75),
Type(0.125 * -3.75));
EXPECT_EQ( EXPECT_EQ(
Type((int)1) / Type(0.5), Type((int)1) / Type(0.5),
Type(2)); Type(2));