blob: add4eea21cf34c4996601816d8be91619b333024 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FFL_FIXED_H_
#define FFL_FIXED_H_
//
// Fuchsia Fixed-point Library (FFL):
//
// An efficient header-only multi-precision fixed point math library with well-
// defined rounding.
//
#include <cstddef>
#include <type_traits>
#include <ffl/expression.h>
#include <ffl/fixed_format.h>
#include <ffl/saturating_arithmetic.h>
#include <ffl/utility.h>
namespace ffl {
// Represents a fixed-point value using the given integer base type |Integer|
// and the given number of fractional bits |FractionalBits|. This type supports
// standard arithmetic operations and comparisons between the same type, fixed-
// point types with different precision/resolution, and integer values.
//
// Arithmetic operations are not immediately computed. Instead, arithmetic
// expressions involving fixed-point types are assembled into intermediate
// expression trees (via the Expression template type) that capture operands and
// order of operations. The value of the expression tree is evaluated when it is
// assigned to a fixed-point variable. Using this approach the precision and
// resolution of intermediate values are selected at compile time, based on the
// final precision and resolution of the destination variable.
//
// See README.md for a more detailed discussion of fixed-point arithmetic,
// rounding, precision, and resolution in this library.
//
template <typename Integer, size_t FractionalBits>
class Fixed {
public:
// Alias of the FixedFormat type describing traits and low-level operations
// on the fixed-point representation of this type.
using Format = FixedFormat<Integer, FractionalBits>;
// Returns the given raw integer as a fixed-point value in this format.
static constexpr Fixed FromRaw(Integer value) {
return ValueExpression<Integer, FractionalBits>{value};
}
// Returns the minimum value of this fixed point format.
static constexpr Fixed Min() { return FromRaw(Format::Min); }
// Returns the maximum value of this fixed point format.
static constexpr Fixed Max() { return FromRaw(Format::Max); }
// Fixed is default constructible without a default value, which is the same
// as for plain integer types. This is permitted in constexpr contexts as
// long as the underling integer member |value_| is initialized before use.
constexpr Fixed() = default;
// Fixed is copy constructible and assignable.
constexpr Fixed(const Fixed&) = default;
constexpr Fixed& operator=(const Fixed&) = default;
// Explicit conversion from an integer value. The value is saturated to fit
// within the integer precision defined by Format::IntegerBits.
explicit constexpr Fixed(Integer value) : Fixed{ToExpression<Integer>{value}} {}
// Implicit conversion from an intermediate expression. The value is converted
// to the precision and resolution of this type, if necessary.
template <Operation Op, typename... Args>
constexpr Fixed(Expression<Op, Args...> expression)
: Fixed{Format::Convert(expression.Evaluate(Format{}))} {}
// Explicit conversion from another fixed point type. The value is converted
// to the precision and resolution of this type, if necessary.
template <typename OtherInteger, size_t OtherFractionalBits,
typename = std::enable_if_t<!std::is_same_v<Integer, OtherInteger> ||
FractionalBits != OtherFractionalBits>>
explicit constexpr Fixed(const Fixed<OtherInteger, OtherFractionalBits>& other)
: Fixed{Format::Convert(other.value())} {}
// Assignment from an intermediate expression. The value is rounded and
// saturated to fit within the precision and resolution of this type, if
// necessary.
template <Operation Op, typename... Args>
constexpr Fixed& operator=(Expression<Op, Args...> expression) {
return *this = Fixed{expression};
}
// Implicit conversion from an intermediate value of the same format.
constexpr Fixed(Value<Format> value) : value_{Format::Saturate(value)} {}
// Assignment from an intermediate value of the same format.
constexpr Fixed& operator=(Value<Format> value) { return *this = Fixed{value}; }
// Returns the raw fixed-point value as the underling integer type.
constexpr Integer raw_value() const { return value_; }
// Returns the fixed-point value as an intermediate value type.
constexpr Value<Format> value() const { return Value<Format>{value_}; }
// Returns the closest integer value greater-than or equal-to this fixed-
// point value.
constexpr Integer Ceiling() const {
const Integer value = value_ / Format::AdjustmentFactor;
const Integer power = Format::AdjustedPower;
const auto saturated_value = Format::IsUnsigned || value >= 0
? SaturateAddAs<Integer>(value, Format::AdjustedFractionalMask)
: value;
return static_cast<Integer>(saturated_value / power);
}
// Returns the closest integer value less-than or equal-to this fixed-point
// value.
constexpr Integer Floor() const {
const Integer value = value_ / Format::AdjustmentFactor;
const Integer power = Format::AdjustedPower;
const Integer masked_value = value & Format::AdjustedIntegralMask;
return static_cast<Integer>(masked_value / power);
}
// Returns the rounded value of this fixed-point value as an integer.
constexpr Integer Round() const {
const Integer value = value_ / Format::AdjustmentFactor;
const Integer power = Format::AdjustedPower;
const Integer rounded_value = Format::Round(value, ToPlace<Format::AdjustedFractionalBits>);
return Format::Saturate(static_cast<Integer>(rounded_value / power));
}
// Returns the integral component of this fixed-point value. The result retains
// the sign of this value. For example, Fixed{-2.5}.Integral() == -2.
//
// This preserves the following invariant:
//
// Fixed f;
// f.Integral() + f.Fraction() == f
//
constexpr Fixed Integral() const {
if constexpr (Format::IntegralBits == 0) {
if constexpr (Format::IsSigned) {
if (*this <= Fixed{-1}) {
return Fixed{-1};
}
}
return Fixed{0};
} else {
return Fixed(value_ / static_cast<Integer>(Format::Power));
}
}
// Returns the fractional component of this fixed-point value. The result retains
// the sign of this value. For example, Fixed(-2.5).Fraction() == -0.5.
//
// See Integral().
constexpr Fixed Fraction() const { return *this - Integral(); }
// Returns the absolute value of this fixed-point value.
constexpr Fixed Absolute() const {
// Compute a mask and bit to conditionally convert |value_| to positive.
// When |value_| is negative, then |mask| = -1 and |one| = 1, otherwise both
// are zero.
const Integer mask = static_cast<Integer>(-(value_ < 0));
const Integer one = mask & 1;
// Find the absolute value by computing the clamped two's complement. This
// is a no-op when |value_| is positive because |mask| and |one| are zero.
// Note that this will always return a positive value by clamping to Max.
Integer absolute = 0;
if (__builtin_add_overflow(value_ ^ mask, one, &absolute)) {
absolute = Format::Max;
}
return FromRaw(absolute);
}
// Relational operators for same-typed values.
constexpr bool operator<(Fixed other) const { return value_ < other.value_; }
constexpr bool operator>(Fixed other) const { return value_ > other.value_; }
constexpr bool operator<=(Fixed other) const { return value_ <= other.value_; }
constexpr bool operator>=(Fixed other) const { return value_ >= other.value_; }
constexpr bool operator==(Fixed other) const { return value_ == other.value_; }
constexpr bool operator!=(Fixed other) const { return value_ != other.value_; }
// Compound assignment operators.
template <typename T, typename Enabled = EnableIfUnaryExpression<T>>
constexpr Fixed& operator+=(T expression) {
*this = *this + expression;
return *this;
}
template <typename T, typename Enabled = EnableIfUnaryExpression<T>>
constexpr Fixed& operator-=(T expression) {
*this = *this - expression;
return *this;
}
template <typename T, typename Enabled = EnableIfUnaryExpression<T>>
constexpr Fixed& operator*=(T expression) {
*this = *this * expression;
return *this;
}
template <typename T, typename Enabled = EnableIfUnaryExpression<T>>
constexpr Fixed& operator/=(T expression) {
*this = *this / expression;
return *this;
}
private:
Integer value_;
};
// Utility to round an expression to the given Integer.
template <typename Integer, typename T, typename Enabled = EnableIfUnaryExpression<T>>
inline constexpr auto Round(T expression) {
const Fixed<Integer, 0> value{ToExpression<T>{expression}};
return value.Round();
}
// Utility to create an Expression node from an integer value.
template <typename Integer, typename Enabled = std::enable_if_t<std::is_integral_v<Integer>>>
inline constexpr auto FromInteger(Integer value) {
return ToExpression<Integer>{value};
}
// Utility to create an Expression node from an integer ratio. May be used to
// initialize a Fixed variable from a ratio.
template <typename Integer, typename Enabled = std::enable_if_t<std::is_integral_v<Integer>>>
inline constexpr auto FromRatio(Integer numerator, Integer denominator) {
return DivisionExpression<Integer, Integer>{numerator, denominator};
}
// Utility to coerce an expression to the given resolution.
template <size_t FractionalBits, typename T>
inline constexpr auto ToResolution(T expression) {
return ResolutionExpression<FractionalBits, T>{Init{}, expression};
}
// Utility to create a value Expression from a raw integer value already in the
// fixed-point format with the given number of fractional bits.
template <size_t FractionalBits, typename Integer>
inline constexpr auto FromRaw(Integer value) {
return ValueExpression<Integer, FractionalBits>{value};
}
// Relational operators.
//
// Fixed-to-fixed comparisons convert to an intermediate type with suitable
// precision and the least resolution of the two operands, using convergent
// rounding to reduce resolution and avoid bias.
//
// Fixed-to-integer comparisons convert to an intermediate type with suitable
// precision and the resolution of the fixed-point type. This is less
// less surprising when comparing a fixed-point type to zero and other integer
// constants.
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator<(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) < Traits::Right(right);
}
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator>(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) > Traits::Right(right);
}
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator<=(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) <= Traits::Right(right);
}
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator>=(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) >= Traits::Right(right);
}
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator==(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) == Traits::Right(right);
}
template <typename Left, typename Right,
typename Enabled = EnableIfComparisonExpression<Left, Right>>
inline constexpr bool operator!=(Left left, Right right) {
using Traits = ComparisonTraits<Left, Right>;
return Traits::Left(left) != Traits::Right(right);
}
// Arithmetic operators. These operators accept any combination of Fixed,
// integer, and Expression (excluding integer/integer which is handled by the
// language). The return type and value captures the operation and operands as
// an Expression for later evaluation. Evaluation is performed when the
// Expression tree is assigned to a Fixed variable. This can be composed in
// multiple stages and assignments.
//
// Example:
//
// const int32_t value = ...;
// cosnt int32_t offset = ...;
//
// const auto quotient = FromRatio(value, 3);
// const Fixed<int32_t, 1> low_precision = quotient;
// const Fixed<int64_t, 10> high_precision = quotient;
//
// const auto with_offset = quotient + ToResolution<10>(offset);
// const Fixed<int64_t, 10> high_precision_with_offset = with_offset;
//
template <typename Left, typename Right, typename Enabled = EnableIfBinaryExpression<Left, Right>>
inline constexpr auto operator+(Left left, Right right) {
return AdditionExpression<Left, Right>{left, right};
}
template <typename T, typename Enabled = EnableIfUnaryExpression<T>>
inline constexpr auto operator-(T value) {
return NegationExpression<T>{Init{}, value};
}
template <typename Left, typename Right, typename Enabled = EnableIfBinaryExpression<Left, Right>>
inline constexpr auto operator-(Left left, Right right) {
return SubtractionExpression<Left, Right>{left, right};
}
template <typename Left, typename Right, typename Enabled = EnableIfBinaryExpression<Left, Right>>
inline constexpr auto operator*(Left left, Right right) {
return MultiplicationExpression<Left, Right>{left, right};
}
template <typename Left, typename Right, typename Enabled = EnableIfBinaryExpression<Left, Right>>
inline constexpr auto operator/(Left left, Right right) {
return DivisionExpression<Left, Right>{left, right};
}
} // namespace ffl
#endif // FFL_FIXED_H_