blob: ae351e17e2956b29d4c88aff956b218d2aea9e57 [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_EXPRESSION_H_
#define FFL_EXPRESSION_H_
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <ffl/fixed_format.h>
#include <ffl/saturating_arithmetic.h>
#include <ffl/utility.h>
namespace ffl {
// Forward declaration.
template <typename Integer, size_t FractionalBits>
class Fixed;
// Enumeration representing the type or function of an Expression.
enum class Operation {
Value,
Addition,
Subtraction,
Multiplication,
Division,
Negation,
Resolution,
};
// Traits type that determines the promoted result format, given an operation
// and input formats.
template <Operation, typename, typename, typename = void>
struct PromoteFormat;
template <typename SourceFormat, typename TargetFormat>
struct PromoteFormat<Operation::Value, SourceFormat, TargetFormat> {
static constexpr bool IsSigned = std::is_signed_v<typename TargetFormat::Integer>;
static constexpr size_t FractionalBits = TargetFormat::FractionalBits;
using Integer =
BestFitting<IsSigned, std::max(SourceFormat::IntegralBits, TargetFormat::IntegralBits) +
FractionalBits>;
using Format = FixedFormat<Integer, FractionalBits>;
};
template <typename LeftFormat, typename RightFormat>
struct PromoteFormat<Operation::Addition, LeftFormat, RightFormat> {
static constexpr bool IsSigned =
std::is_signed_v<decltype(std::declval<typename LeftFormat::Integer>() +
std::declval<typename RightFormat::Integer>())>;
static constexpr size_t FractionalBits =
std::min(LeftFormat::FractionalBits, RightFormat::FractionalBits);
using Integer =
BestFitting<IsSigned, std::max(LeftFormat::PositiveBits, RightFormat::PositiveBits) + 1>;
using Format = FixedFormat<Integer, FractionalBits>;
};
template <typename LeftFormat, typename RightFormat>
struct PromoteFormat<Operation::Subtraction, LeftFormat, RightFormat> {
static constexpr bool IsSigned =
std::is_signed_v<decltype(std::declval<typename LeftFormat::Integer>() -
std::declval<typename RightFormat::Integer>())>;
static constexpr size_t FractionalBits =
std::min(LeftFormat::FractionalBits, RightFormat::FractionalBits);
using Integer =
BestFitting<IsSigned, std::max(LeftFormat::PositiveBits, RightFormat::PositiveBits) + 1>;
using Format = FixedFormat<Integer, FractionalBits>;
};
template <typename LeftFormat, typename RightFormat>
struct PromoteFormat<Operation::Multiplication, LeftFormat, RightFormat> {
static constexpr bool IsSigned =
std::is_signed_v<decltype(std::declval<typename LeftFormat::Integer>() *
std::declval<typename RightFormat::Integer>())>;
static constexpr size_t FractionalBits = LeftFormat::FractionalBits + RightFormat::FractionalBits;
static constexpr size_t IntegralBits =
LeftFormat::IntegralBits + RightFormat::IntegralBits + (IsSigned ? 1 : 0);
using Integer = BestFitting<IsSigned, IntegralBits + FractionalBits>;
using Format = FixedFormat<Integer, FractionalBits>;
};
template <typename LeftFormat, typename RightFormat, typename TargetFormat>
struct PromoteFormat<Operation::Division, LeftFormat, RightFormat, TargetFormat> {
static constexpr bool IsSigned =
std::is_signed_v<decltype(std::declval<typename LeftFormat::Integer>() /
std::declval<typename RightFormat::Integer>())>;
static constexpr size_t FractionalBits =
TargetFormat::FractionalBits + RightFormat::FractionalBits;
static constexpr size_t IntegralBits =
LeftFormat::IntegralBits + RightFormat::FractionalBits + (IsSigned ? 1 : 0);
using Integer = BestFitting<IsSigned, IntegralBits + FractionalBits>;
using NumeratorFormat = FixedFormat<Integer, FractionalBits>;
using QuotientFormat = FixedFormat<Integer, TargetFormat::FractionalBits>;
};
// Type representing a node in an expression tree. Specializations implement the
// various types of expression nodes and their behavior. A specialization must
// have a template method to perform evaluation compatible with the following
// signature:
//
// template <typename TargetFormat>
// constexpr auto Evaluate(TargetFormat) const { ... }
//
// The |TargetFormat| template parameter is an instantiation of FixedFormat to
// provide a hint about the final format of the evaluated expression. This may
// be used to make resolution optimization decisions however, the result of the
// Evaluate method is not required to be in TargetFormat.
//
// The return value of Evaluate must be an instance of Value<> and may be in any
// format suitable to the result of the expression node evaluation.
//
template <Operation, typename... Args>
struct Expression;
// Specialization for immediate values in a particular format. This expression
// node takes a single template argument for the format of the value to store.
template <typename Integer, size_t FractionalBits>
struct Expression<Operation::Value, FixedFormat<Integer, FractionalBits>> {
using Format = FixedFormat<Integer, FractionalBits>;
// Constructs the expression node from a raw integer value already in the
// fixed-point format specified by Format.
explicit constexpr Expression(Integer raw_value) : value{raw_value} {}
// Constructs the expression node from a Fixed instance of the same format.
explicit constexpr Expression(Fixed<Integer, FractionalBits> fixed) : value{fixed.raw_value()} {}
const Value<Format> value;
// Returns the underlying value. TargetFormat is ignored, conversion to the
// final format is handled by the Fixed constructor or assignment operator.
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat) const {
return value;
}
};
// Specialization for negation of a subexpression. This expression node takes a
// single template argument for the subexpression to negate.
template <Operation Op, typename... Args>
struct Expression<Operation::Negation, Expression<Op, Args...>> {
template <typename T>
constexpr Expression(Init, T&& value) : value{std::forward<T>(value)} {}
const Expression<Op, Args...> value;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat target_format) const {
return Perform(value.Evaluate(target_format));
}
private:
template <typename TargetFormat>
static constexpr auto Perform(Value<TargetFormat> value) {
using Integer = typename TargetFormat::Integer;
const Integer result = -value.value;
return Value<TargetFormat>{result};
}
};
// Specialization to coerce the precision of a subexpression. This expression
// node takes template arguments for the target precision and subexpression to
// coerce.
template <size_t FractionalBits, Operation Op, typename... Args>
struct Expression<Operation::Resolution, Resolution<FractionalBits>, Expression<Op, Args...>> {
template <typename T>
constexpr Expression(Init, T&& value) : value{std::forward<T>(value)} {}
const Expression<Op, Args...> value;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat) const {
using Integer =
BestFitting<TargetFormat::IsSigned, TargetFormat::IntegralBits + FractionalBits>;
using IntermediateFormat = FixedFormat<Integer, FractionalBits>;
return IntermediateFormat::Convert(value.Evaluate(IntermediateFormat{}));
}
};
// Specialization for addition of subexpressions. This expression node takes two
// template arguments for the left-hand and right-hand subexpressions to add.
template <Operation LeftOp, Operation RightOp, typename... LeftArgs, typename... RightArgs>
struct Expression<Operation::Addition, Expression<LeftOp, LeftArgs...>,
Expression<RightOp, RightArgs...>> {
template <typename L, typename R>
constexpr Expression(L&& left, R&& right)
: left{std::forward<L>(left)}, right{std::forward<R>(right)} {}
const Expression<LeftOp, LeftArgs...> left;
const Expression<RightOp, RightArgs...> right;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat target_format) const {
return Perform(left.Evaluate(target_format), right.Evaluate(target_format));
}
private:
template <typename LeftFormat, typename RightFormat>
static constexpr auto Perform(Value<LeftFormat> left, Value<RightFormat> right) {
using Promote = PromoteFormat<Operation::Addition, LeftFormat, RightFormat>;
using IntermediateFormat = typename Promote::Format;
using Integer = typename IntermediateFormat::Integer;
const auto left_value = IntermediateFormat::Convert(left);
const auto right_value = IntermediateFormat::Convert(right);
return Value<IntermediateFormat>{SaturateAddAs<Integer>(left_value.value, right_value.value)};
}
};
// Specialization for subtraction of subexpressions. This expression node takes
// two template arguments for the left-hand and right-hand subexpressions to
// subtract.
template <Operation LeftOp, Operation RightOp, typename... LeftArgs, typename... RightArgs>
struct Expression<Operation::Subtraction, Expression<LeftOp, LeftArgs...>,
Expression<RightOp, RightArgs...>> {
template <typename L, typename R>
constexpr Expression(L&& left, R&& right)
: left{std::forward<L>(left)}, right{std::forward<R>(right)} {}
const Expression<LeftOp, LeftArgs...> left;
const Expression<RightOp, RightArgs...> right;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat target_format) const {
return Perform(left.Evaluate(target_format), right.Evaluate(target_format));
}
private:
template <typename LeftFormat, typename RightFormat>
static constexpr auto Perform(Value<LeftFormat> left, Value<RightFormat> right) {
using Promote = PromoteFormat<Operation::Subtraction, LeftFormat, RightFormat>;
using IntermediateFormat = typename Promote::Format;
using Integer = typename IntermediateFormat::Integer;
const auto left_value = IntermediateFormat::Convert(left);
const auto right_value = IntermediateFormat::Convert(right);
return Value<IntermediateFormat>{
SaturateSubtractAs<Integer>(left_value.value, right_value.value)};
}
};
// Specialization for multiplication of subexpressions. This expression node
// takes two template arguments for the left-hand and right-hand subexpressions
// to multiply.
template <Operation LeftOp, Operation RightOp, typename... LeftArgs, typename... RightArgs>
struct Expression<Operation::Multiplication, Expression<LeftOp, LeftArgs...>,
Expression<RightOp, RightArgs...>> {
template <typename L, typename R>
constexpr Expression(L&& left, R&& right)
: left{std::forward<L>(left)}, right{std::forward<R>(right)} {}
const Expression<LeftOp, LeftArgs...> left;
const Expression<RightOp, RightArgs...> right;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat target_format) const {
return Perform(left.Evaluate(target_format), right.Evaluate(target_format));
}
private:
template <typename LeftFormat, typename RightFormat>
static constexpr auto Perform(Value<LeftFormat> left, Value<RightFormat> right) {
using Promote = PromoteFormat<Operation::Multiplication, LeftFormat, RightFormat>;
using IntermediateFormat = typename Promote::Format;
using Integer = typename IntermediateFormat::Integer;
return Value<IntermediateFormat>{SaturateMultiplyAs<Integer>(left.value, right.value)};
}
};
// Specialization for division of subexpressions. This expression node takes two
// template arguments for the left-hand and right-hand subexpressions to divide.
template <Operation LeftOp, Operation RightOp, typename... LeftArgs, typename... RightArgs>
struct Expression<Operation::Division, Expression<LeftOp, LeftArgs...>,
Expression<RightOp, RightArgs...>> {
template <typename L, typename R>
constexpr Expression(L&& left, R&& right)
: left{std::forward<L>(left)}, right{std::forward<R>(right)} {}
const Expression<LeftOp, LeftArgs...> left;
const Expression<RightOp, RightArgs...> right;
template <typename TargetFormat>
constexpr auto Evaluate(TargetFormat target_format) const {
return Perform(target_format, left.Evaluate(target_format), right.Evaluate(target_format));
}
private:
template <typename TargetFormat, typename LeftFormat, typename RightFormat>
static constexpr auto Perform(TargetFormat, Value<LeftFormat> left, Value<RightFormat> right) {
using Promote = PromoteFormat<Operation::Division, LeftFormat, RightFormat, TargetFormat>;
using NumeratorFormat = typename Promote::NumeratorFormat;
using QuotientFormat = typename Promote::QuotientFormat;
using Integer = typename QuotientFormat::Integer;
const auto quotient = NumeratorFormat::Convert(left).value / right.value;
return Value<QuotientFormat>{static_cast<Integer>(quotient)};
}
};
// Traits type to determine whether some type T may be converted to
// an Expression and the specific type of Expression it converts to.
template <typename T, typename Enabled = void>
struct ExpressionTraits : std::false_type {};
template <typename Integer, size_t FractionalBits>
struct ExpressionTraits<Fixed<Integer, FractionalBits>> : std::true_type {
using ExpressionType =
Expression<Operation::Value, typename Fixed<Integer, FractionalBits>::Format>;
};
template <Operation Op, typename... Args>
struct ExpressionTraits<Expression<Op, Args...>> : std::true_type {
using ExpressionType = Expression<Op, Args...>;
};
template <typename T>
struct ExpressionTraits<T, std::enable_if_t<std::is_integral_v<T>>> : std::true_type {
using ExpressionType = Expression<Operation::Value, FixedFormat<T, 0>>;
};
// Utility type to convert from T to its associated Expression.
template <typename T>
using ToExpression = typename ExpressionTraits<T>::ExpressionType;
// Traits type to determine whether two types may be compared. Provides Left and
// Right conversion operations to convert to a common format for comparison.
//
// Any combination of integer, Fixed<>, and Expression<> are supported,
// excluding integer-integer and Expression-Expression comparisons; integer-
// integer comparisons are already handled by the language, whereas Expression-
// Expression comparisons are excluded because expressions do not have a
// definite resolution until assigned.
//
// To compare two expressions explicitly convert at least one side to Fixed<>.
template <typename Right, typename Left, typename Enabled = void>
struct ComparisonTraits : std::false_type {};
// Specialization for comparison of two Fixed values. Values are converted to
// an common format with suitable precision and the least resolution.
template <typename LeftInteger, size_t LeftFractionalBits, typename RightInteger,
size_t RightFractionalBits>
struct ComparisonTraits<
Fixed<LeftInteger, LeftFractionalBits>, Fixed<RightInteger, RightFractionalBits>,
std::enable_if_t<std::is_signed_v<LeftInteger> == std::is_signed_v<RightInteger>>>
: std::true_type {
// Extract the integral bits of each format.
static constexpr size_t LeftIntegralBits =
Fixed<LeftInteger, LeftFractionalBits>::Format::IntegralBits;
static constexpr size_t RightIntegralBits =
Fixed<RightInteger, RightFractionalBits>::Format::IntegralBits;
// Use the least of the fractional bits of each format.
static constexpr size_t TargetFractionalBits = std::min(LeftFractionalBits, RightFractionalBits);
static constexpr size_t TargetIntegralBits = std::max(LeftIntegralBits, RightIntegralBits);
static constexpr bool IsSigned = std::is_signed_v<LeftInteger> && std::is_signed_v<RightInteger>;
// Use the best fitting integer that can accommodate the max range and min resolution.
using TargetInteger = BestFitting<IsSigned, TargetIntegralBits + TargetFractionalBits>;
using TargetType = Fixed<TargetInteger, TargetFractionalBits>;
static constexpr auto Left(Fixed<LeftInteger, LeftFractionalBits> value) {
return TargetType{TargetType::Format::Convert(value.value())};
}
static constexpr auto Right(Fixed<RightInteger, RightFractionalBits> value) {
return TargetType{TargetType::Format::Convert(value.value())};
}
};
// Specialization for comparing Fixed with Expression. The expression is
// evaluated and converted to the same format as Fixed before comparison.
template <typename Integer, size_t FractionalBits, Operation Op, typename... Args>
struct ComparisonTraits<Fixed<Integer, FractionalBits>, Expression<Op, Args...>> : std::true_type {
static constexpr auto Left(Fixed<Integer, FractionalBits> value) { return value; }
static constexpr auto Right(Expression<Op, Args...> expression) {
return Fixed<Integer, FractionalBits>{expression};
}
};
// Specialization for comparing Expression with Fixed. The expression is
// evaluated and converted to the same format as Fixed before comparison.
template <typename Integer, size_t FractionalBits, Operation Op, typename... Args>
struct ComparisonTraits<Expression<Op, Args...>, Fixed<Integer, FractionalBits>> : std::true_type {
static constexpr auto Left(Expression<Op, Args...> expression) {
return Fixed<Integer, FractionalBits>{expression};
}
static constexpr auto Right(Fixed<Integer, FractionalBits> value) { return value; }
};
// Specialization for comparing Fixed with integer. Both values are converted to
// a common format with suitable precision and the same resolution as the fixed
// argument.
template <typename Integer, size_t FractionalBits, typename T>
struct ComparisonTraits<
Fixed<Integer, FractionalBits>, T,
std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<Integer> == std::is_signed_v<T>>>
: std::true_type {
using TargetInteger =
BestFitting<std::is_signed_v<Integer>,
std::max(IntegerPrecision<Integer>, IntegerPrecision<T> + FractionalBits)>;
using TargetType = Fixed<TargetInteger, FractionalBits>;
static constexpr auto Left(Fixed<Integer, FractionalBits> value) {
return TargetType{TargetType::Format::Convert(value.value())};
}
static constexpr auto Right(T value) { return TargetType{ToExpression<T>(value)}; }
};
// Specialization for comparing integer with Fixed. Both values are converted to
// a common format with suitable precision and the same resolution as the fixed
// argument.
template <typename Integer, size_t FractionalBits, typename T>
struct ComparisonTraits<
T, Fixed<Integer, FractionalBits>,
std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<Integer> == std::is_signed_v<T>>>
: std::true_type {
using TargetInteger =
BestFitting<std::is_signed_v<Integer>,
std::max(IntegerPrecision<Integer>, IntegerPrecision<T> + FractionalBits)>;
using TargetType = Fixed<TargetInteger, FractionalBits>;
static constexpr auto Left(T value) { return TargetType{ToExpression<T>(value)}; }
static constexpr auto Right(Fixed<Integer, FractionalBits> value) {
return TargetType{TargetType::Format::Convert(value.value())};
}
};
// TODO(eieio): Integer-Expression comparisons.
// Enable if Left and Right are comparable.
template <typename Right, typename Left, typename Return = void>
using EnableIfComparisonExpression = std::enable_if_t<ComparisonTraits<Left, Right>::value, Return>;
// Alias for a value expression node type.
template <typename Integer, size_t FractionalBits>
using ValueExpression = Expression<Operation::Value, FixedFormat<Integer, FractionalBits>>;
// Alias for a negation expression node type.
template <typename T>
using NegationExpression = Expression<Operation::Negation, ToExpression<T>>;
// Alias for a precision expression node type.
template <size_t FractionalBits, typename T>
using ResolutionExpression =
Expression<Operation::Resolution, Resolution<FractionalBits>, ToExpression<T>>;
// Alias for an addition expression node type.
template <typename Left, typename Right>
using AdditionExpression = Expression<Operation::Addition, ToExpression<Left>, ToExpression<Right>>;
// Alias for an subtraction expression node type.
template <typename Left, typename Right>
using SubtractionExpression =
Expression<Operation::Subtraction, ToExpression<Left>, ToExpression<Right>>;
// Alias for an multiplication expression node type.
template <typename Left, typename Right>
using MultiplicationExpression =
Expression<Operation::Multiplication, ToExpression<Left>, ToExpression<Right>>;
// Alias for an multiplication expression node type.
template <typename Left, typename Right>
using DivisionExpression = Expression<Operation::Division, ToExpression<Left>, ToExpression<Right>>;
// Enable if T can be converted into a unary expression node.
template <typename T, typename Return = void>
using EnableIfUnaryExpression = std::enable_if_t<ExpressionTraits<T>::value, Return>;
// Enable if T and U can be converted into a binary expression node.
template <typename T, typename U, typename Return = void>
using EnableIfBinaryExpression =
std::enable_if_t<ExpressionTraits<T>::value && ExpressionTraits<U>::value &&
!(std::is_integral_v<T> && std::is_integral_v<U>),
Return>;
} // namespace ffl
#endif // FFL_EXPRESSION_H_