ladybird/AK/FixedPoint.h
Nico Weber b898a46d7f AK: Make FixedPoint(FloatingPoint) ctor round instead of truncating
This is needed to have code for creating an in-memory sRGB profile using
the (floating-ppoint) numbers from the sRGB spec and having the
fixed-point values in the profile match what they are in other software
(such as GIMP).

It has the side effect of making the FixedPoint ctor no longer constexpr
(which seems fine; nothing was currently relying on that).

Some of FixedPoint's member functions don't round yet, which requires
tweaking a test.
2023-03-03 09:23:02 +00:00

449 lines
12 KiB
C++

/*
* Copyright (c) 2021, Leon Albrecht <leon2002.la@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/Format.h>
#include <AK/IntegralMath.h>
#include <AK/NumericLimits.h>
#include <AK/Types.h>
#ifndef KERNEL
# include <AK/Math.h>
#endif
namespace AK {
// FIXME: this always uses round to nearest break-tie to even
// FIXME: use the Integral concept to constrain Underlying
template<size_t precision, typename Underlying>
class FixedPoint {
using This = FixedPoint<precision, Underlying>;
constexpr static Underlying radix_mask = (static_cast<Underlying>(1) << precision) - 1;
template<size_t P, typename U>
friend class FixedPoint;
public:
constexpr FixedPoint() = default;
template<Integral I>
constexpr FixedPoint(I value)
: m_value(static_cast<Underlying>(value) << precision)
{
}
#ifndef KERNEL
template<FloatingPoint F>
FixedPoint(F value)
: m_value(round_to<Underlying>(value * (static_cast<Underlying>(1) << precision)))
{
}
#endif
template<size_t P, typename U>
explicit constexpr FixedPoint(FixedPoint<P, U> const& other)
: m_value(other.template cast_to<precision, Underlying>().m_value)
{
}
#ifndef KERNEL
template<FloatingPoint F>
explicit ALWAYS_INLINE operator F() const
{
return (F)m_value * pow<F>(0.5, precision);
}
#endif
template<Integral I>
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;
}
static constexpr This create_raw(Underlying value)
{
This t {};
t.raw() = value;
return t;
}
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 clamp(This minimum, This maximum) const
{
if (*this < minimum)
return minimum;
if (*this > maximum)
return maximum;
return *this;
}
constexpr This round() const
{
return This { static_cast<Underlying>(*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<Underlying>(*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);
}
// http://www.claysturner.com/dsp/BinaryLogarithm.pdf
constexpr This log2() const
{
// 0.5
This b = create_raw(1 << (precision - 1));
This y = 0;
This x = *this;
// FIXME: There's no negative infinity.
if (x.raw() <= 0)
return create_raw(NumericLimits<Underlying>::min());
if (x != 1) {
i32 shift_amount = AK::log2<Underlying>(x.raw()) - precision;
if (shift_amount > 0)
x >>= shift_amount;
else
x <<= -shift_amount;
y += shift_amount;
}
for (size_t i = 0; i < precision; ++i) {
x *= x;
if (x >= 2) {
x >>= 1;
y += b;
}
b >>= 1;
}
return y;
}
constexpr bool signbit() const
requires(IsSigned<Underlying>)
{
return m_value >> (sizeof(Underlying) * 8 - 1);
}
constexpr This operator-() const
requires(IsSigned<Underlying>)
{
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<Integral I>
constexpr This operator+(I other) const
{
return create_raw(m_value + (other << precision));
}
template<Integral I>
constexpr This operator-(I other) const
{
return create_raw(m_value - (other << precision));
}
template<Integral I>
constexpr This operator*(I other) const
{
return create_raw(m_value * other);
}
template<Integral I>
constexpr This operator/(I other) const
{
return create_raw(m_value / other);
}
template<Integral I>
constexpr This operator>>(I other) const
{
return create_raw(m_value >> other);
}
template<Integral I>
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<Integral I>
This& operator+=(I other)
{
m_value += other << precision;
return *this;
}
template<Integral I>
This& operator-=(I other)
{
m_value -= other << precision;
return *this;
}
template<Integral I>
This& operator*=(I other)
{
m_value *= other;
return *this;
}
template<Integral I>
This& operator/=(I other)
{
m_value /= other;
return *this;
}
template<Integral I>
This& operator>>=(I other)
{
m_value >>= other;
return *this;
}
template<Integral I>
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<Integral I>
bool operator==(I other) const
{
return m_value >> precision == other && !(m_value & radix_mask);
}
template<Integral I>
bool operator!=(I other) const
{
return (m_value >> precision) != other || m_value & radix_mask;
}
template<Integral I>
bool operator>(I other) const
{
return !(*this <= other);
}
template<Integral I>
bool operator>=(I other) const
{
return !(*this < other);
}
template<Integral I>
bool operator<(I other) const
{
return (m_value >> precision) < other || m_value < (other << precision);
}
template<Integral I>
bool operator<=(I other) const
{
return *this < other || *this == other;
}
// Casting from a float should be faster than casting to a float
template<FloatingPoint F>
bool operator==(F other) const { return *this == (This)other; }
template<FloatingPoint F>
bool operator!=(F other) const { return *this != (This)other; }
template<FloatingPoint F>
bool operator>(F other) const { return *this > (This)other; }
template<FloatingPoint F>
bool operator>=(F other) const { return *this >= (This)other; }
template<FloatingPoint F>
bool operator<(F other) const { return *this < (This)other; }
template<FloatingPoint F>
bool operator<=(F other) const { return *this <= (This)other; }
template<size_t P, typename U>
operator FixedPoint<P, U>() const
{
return cast_to<P, U>();
}
private:
template<size_t P, typename U>
constexpr FixedPoint<P, U> cast_to() const
{
U raw_value = static_cast<U>(m_value >> precision) << P;
if constexpr (precision > P)
raw_value |= (m_value & radix_mask) >> (precision - P);
else if constexpr (precision < P)
raw_value |= static_cast<U>(m_value & radix_mask) << (P - precision);
else
raw_value |= m_value & radix_mask;
return FixedPoint<P, U>::create_raw(raw_value);
}
Underlying m_value;
};
template<size_t precision, typename Underlying>
struct Formatter<FixedPoint<precision, Underlying>> : StandardFormatter {
Formatter() = default;
explicit Formatter(StandardFormatter formatter)
: StandardFormatter(formatter)
{
}
ErrorOr<void> format(FormatBuilder& builder, FixedPoint<precision, Underlying> value)
{
u8 base;
bool upper_case;
FormatBuilder::RealNumberDisplayMode real_number_display_mode = FormatBuilder::RealNumberDisplayMode::General;
if (m_mode == Mode::Default || m_mode == Mode::FixedPoint) {
base = 10;
upper_case = false;
if (m_mode == Mode::FixedPoint)
real_number_display_mode = FormatBuilder::RealNumberDisplayMode::FixedPoint;
} else if (m_mode == Mode::Hexfloat) {
base = 16;
upper_case = false;
} else if (m_mode == Mode::HexfloatUppercase) {
base = 16;
upper_case = true;
} else {
VERIFY_NOT_REACHED();
}
m_width = m_width.value_or(0);
m_precision = m_precision.value_or(6);
bool is_negative = false;
if constexpr (IsSigned<Underlying>)
is_negative = value < 0;
i64 integer = value.ltrunk();
constexpr u64 one = static_cast<Underlying>(1) << precision;
u64 fraction_raw = value.raw() & (one - 1);
return builder.put_fixed_point(is_negative, integer, fraction_raw, one, base, upper_case, m_zero_pad, m_align, m_width.value(), m_precision.value(), m_fill, m_sign_mode, real_number_display_mode);
}
};
}
#if USING_AK_GLOBALLY
using AK::FixedPoint;
#endif