diff --git a/AK/FixedPoint.h b/AK/FixedPoint.h new file mode 100644 index 0000000000..110f15a4de --- /dev/null +++ b/AK/FixedPoint.h @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace AK { + +// FIXME: this always uses round to nearest break-tie to even +template +class FixedPoint { + using This = FixedPoint; + constexpr static Underlying radix_mask = (1 << precision) - 1; + +public: + constexpr FixedPoint() = default; + template + constexpr FixedPoint(I value) + : m_value(value << precision) + { + } + + template + constexpr FixedPoint(F value) + : m_value(static_cast(value * (1u << precision))) + { + } + + template + explicit ALWAYS_INLINE operator F() const + { + return (F)m_value * pow(0.5, precision); + } + template + explicit constexpr operator I() const + { + I value = m_value >> precision; + // fract(m_value) >= .5? + if (m_value & (1u << (precision - 1))) { + // fract(m_value) > .5? + if (m_value & (radix_mask >> 2u)) { + // yes: round "up"; + value += (m_value > 0 ? 1 : -1); + } else { + // no: round to even; + value += value & 1; + } + } + return value; + } + + constexpr Underlying raw() const + { + return m_value; + } + constexpr Underlying& raw() + { + return m_value; + } + + constexpr This fract() const + { + return create_raw(m_value & radix_mask); + } + + constexpr This round() const + { + return This { static_cast(*this) }; + } + constexpr This floor() const + { + return create_raw(m_value & ~radix_mask); + } + constexpr This ceil() const + { + return create_raw((m_value & ~radix_mask) + + (m_value & radix_mask ? 1 << precision : 0)); + } + constexpr This trunk() const + { + return create_raw((m_value & ~radix_mask) + + ((m_value & radix_mask) + ? (m_value > 0 ? 0 : (1 << precision)) + : 0)); + } + + constexpr Underlying lround() const { return static_cast(*this); } + constexpr Underlying lfloor() const { return m_value >> precision; } + constexpr Underlying lceil() const + { + return (m_value >> precision) + + (m_value & radix_mask ? 1 : 0); + } + constexpr Underlying ltrunk() const + { + return (m_value >> precision) + + ((m_value & radix_mask) + ? m_value > 0 ? 0 : 1 + : 0); + } + + constexpr bool signbit() const requires(IsSigned) + { + return m_value >> (sizeof(Underlying) * 8 - 1); + } + + constexpr This operator-() const requires(IsSigned) + { + return create_raw(-m_value); + } + + constexpr This operator+(This const& other) const + { + return create_raw(m_value + other.m_value); + } + constexpr This operator-(This const& other) const + { + return create_raw(m_value - other.m_value); + } + constexpr This operator*(This const& other) const + { + // FIXME: Potential Overflow, although result could be represented accurately + Underlying value = m_value * other.raw(); + This ret {}; + ret.raw() = value >> precision; + // fract(value) >= .5? + if (value & (1u << (precision - 1))) { + // fract(value) > .5? + if (value & (radix_mask >> 2u)) { + // yes: round up; + ret.raw() += (value > 0 ? 1 : -1); + } else { + // no: round to even (aka unset last sigificant bit); + ret.raw() += m_value & 1; + } + } + return ret; + } + constexpr This operator/(This const& other) const + { + // FIXME: Better rounding? + return create_raw((m_value / other.m_value) << (precision)); + } + + template + constexpr This operator+(I other) const + { + return create_raw(m_value + (other << precision)); + } + template + constexpr This operator-(I other) const + { + return create_raw(m_value - (other << precision)); + } + template + constexpr This operator*(I other) const + { + return create_raw(m_value * other); + } + template + constexpr This operator/(I other) const + { + return create_raw(m_value / other); + } + + This& operator+=(This const& other) + { + m_value += other.raw(); + return *this; + } + This& operator-=(This const& other) + { + m_value -= other.raw(); + return *this; + } + This& operator*=(This const& other) + { + Underlying value = m_value * other.raw(); + 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; + } + This& operator/=(This const& other) + { + // FIXME: See above + m_value /= other.raw(); + m_value <<= precision; + return *this; + } + + template + This& operator+=(I other) + { + m_value += other << precision; + return *this; + } + template + This& operator-=(I other) + { + m_value -= other << precision; + return *this; + } + template + This& operator*=(I other) + { + m_value *= other; + return *this; + } + template + This& operator/=(I other) + { + m_value /= other; + return *this; + } + + bool operator==(This const& other) const { return raw() == other.raw(); } + bool operator!=(This const& other) const { return raw() != other.raw(); } + bool operator>(This const& other) const { return raw() > other.raw(); } + bool operator>=(This const& other) const { return raw() >= other.raw(); } + bool operator<(This const& other) const { return raw() < other.raw(); } + bool operator<=(This const& other) const { return raw() <= other.raw(); } + + // FIXE: There are probably better ways to do these + template + bool operator==(I other) const + { + return m_value >> precision == other && !(m_value & radix_mask); + } + template + bool operator!=(I other) const + { + return (m_value >> precision) != other || m_value & radix_mask; + } + template + bool operator>(I other) const + { + if (m_value > 0) + return (m_value >> precision) > other || (m_value >> precision == other && (m_value & radix_mask)); + if (other > 0) + return false; + + return (m_value >> precision) > other || !(m_value >> precision == other && (m_value & radix_mask)); + } + template + bool operator>=(I other) const + { + if (m_value > 0) + return (m_value >> precision) >= other || (m_value >> precision == other && (m_value & radix_mask)); + if (other > 0) + return false; + + return (m_value >> precision) >= other || !(m_value >> precision == other && (m_value & radix_mask)); + } + template + bool operator<(I other) const + { + if (m_value > 0) + return (m_value >> precision) < other || !(m_value >> precision == other && (m_value & radix_mask)); + if (other > 0) + return true; + + return (m_value >> precision) < other || (m_value >> precision == other && (m_value & radix_mask)); + } + template + bool operator<=(I other) const + { + if (m_value > 0) + return (m_value >> precision) <= other || !(m_value >> precision == other && (m_value & radix_mask)); + if (other > 0) + return true; + + return (m_value >> precision) <= other || (m_value >> precision == other && (m_value & radix_mask)); + } + + // Casting from a float should be faster than casting to a float + template + bool operator==(F other) const { return *this == (This)other; } + template + bool operator!=(F other) const { return *this != (This)other; } + template + bool operator>(F other) const { return *this > (This)other; } + template + bool operator>=(F other) const { return *this >= (This)other; } + template + bool operator<(F other) const { return *this < (This)other; } + template + bool operator<=(F other) const { return *this <= (This)other; } + +private: + static This create_raw(Underlying value) + { + This t {}; + t.raw() = value; + return t; + } + + Underlying m_value; +}; + +template +struct Formatter> : StandardFormatter { + Formatter() = default; + explicit Formatter(StandardFormatter formatter) + : StandardFormatter(formatter) + { + } + + void format(FormatBuilder& builder, FixedPoint value) + { + Formatter formatter { *this }; + formatter.format(builder, (double)value); + } +}; + +} + +using AK::FixedPoint; diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index ba5f1fb7c0..759ca6aa68 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -1,4 +1,5 @@ set(AK_TEST_SOURCES + TestFixedPoint.cpp TestAllOf.cpp TestAnyOf.cpp TestArray.cpp diff --git a/Tests/AK/TestFixedPoint.cpp b/Tests/AK/TestFixedPoint.cpp new file mode 100644 index 0000000000..83d1271f67 --- /dev/null +++ b/Tests/AK/TestFixedPoint.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include + +using Type = FixedPoint<4>; + +TEST_CASE(arithmetic) +{ + EXPECT_EQ( + Type(0.5) + Type(0.5), + Type(1)); + EXPECT_EQ( + Type(1) + Type(0.5), + Type(1.5)); + EXPECT_EQ( + (float)(Type(1) + Type(0.5)), + 1.5f); + // FIXME: Test for rounded multiply + EXPECT_EQ( + Type((int)1) * Type(0.5), + Type(0.5)); + EXPECT_EQ( + Type((int)1) / Type(0.5), + Type(2)); +} + +TEST_CASE(rounding) +{ + EXPECT_EQ(Type(0.5).round(), Type(0)); + EXPECT_EQ(Type(0.5).floor(), Type(0)); + EXPECT_EQ(Type(0.5).ceil(), Type(1)); + EXPECT_EQ(Type(0.75).trunk(), Type(0)); + + EXPECT_EQ(Type(1.5).round(), Type(2)); + EXPECT_EQ(Type(1.5).floor(), Type(1)); + EXPECT_EQ(Type(1.5).ceil(), Type(2)); + EXPECT_EQ(Type(1.25).trunk(), Type(1)); + + EXPECT_EQ(Type(-0.5).round(), Type(0)); + EXPECT_EQ(Type(-0.5).floor(), Type(-1)); + EXPECT_EQ(Type(-0.5).ceil(), Type(0)); + EXPECT_EQ(Type(-0.75).trunk(), Type(0)); + + EXPECT_EQ(Type(-1.5).round(), Type(-2)); + EXPECT_EQ(Type(-1.5).floor(), Type(-2)); + EXPECT_EQ(Type(-1.5).ceil(), Type(-1)); + EXPECT_EQ(Type(-1.25).trunk(), Type(-1)); + + EXPECT_EQ(Type(0.5).lround(), 0); + EXPECT_EQ(Type(0.5).lfloor(), 0); + EXPECT_EQ(Type(0.5).lceil(), 1); + EXPECT_EQ(Type(0.5).ltrunk(), 0); + + EXPECT_EQ(Type(1.5).lround(), 2); + EXPECT_EQ(Type(1.5).lfloor(), 1); + EXPECT_EQ(Type(1.5).lceil(), 2); + EXPECT_EQ(Type(1.5).ltrunk(), 1); + + EXPECT_EQ(Type(-0.5).lround(), 0); + EXPECT_EQ(Type(-0.5).lfloor(), -1); + EXPECT_EQ(Type(-0.5).lceil(), 0); + EXPECT_EQ(Type(-0.5).ltrunk(), 0); + + EXPECT_EQ(Type(-1.5).lround(), -2); + EXPECT_EQ(Type(-1.5).lfloor(), -2); + EXPECT_EQ(Type(-1.5).lceil(), -1); + EXPECT_EQ(Type(-1.5).ltrunk(), -1); +}