blob: 24fea2701d358c368d1d0637c0f873b6c033ed56 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#pragma once
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
#include <ffl/fixed_format.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,
};
// 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 Format>
static constexpr auto Perform(Value<Format> value) {
return Value<Format>{-value.value};
}
};
// 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 Intermediate = typename TargetFormat::Integer;
using IntermediateFormat = FixedFormat<Intermediate, 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) {
// Select the intermediate precision based on the argument format with
// the largest precision.
using Integer = std::conditional_t<LeftFormat::Bits >= RightFormat::Bits,
typename LeftFormat::Integer,
typename RightFormat::Integer>;
// Select the intermediate resolution based on the argument format with
// the least resolution.
const size_t FractionalBits = std::min(LeftFormat::FractionalBits,
RightFormat::FractionalBits);
using IntermediateFormat = FixedFormat<Integer, FractionalBits>;
using Intermediate = typename IntermediateFormat::Intermediate;
const auto left_value = IntermediateFormat::Convert(left);
const auto right_value = IntermediateFormat::Convert(right);
const auto value = static_cast<Intermediate>(left_value.value + right_value.value);
return Value<IntermediateFormat>{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) {
// Select the intermediate precision based on the argument format with
// the largest precision.
using Integer = std::conditional_t<LeftFormat::Bits >= RightFormat::Bits,
typename LeftFormat::Integer,
typename RightFormat::Integer>;
// Select the intermediate resolution based on the argument format with
// the least resolution.
const size_t FractionalBits = std::min(LeftFormat::FractionalBits,
RightFormat::FractionalBits);
using IntermediateFormat = FixedFormat<Integer, FractionalBits>;
using Intermediate = typename IntermediateFormat::Intermediate;
const auto left_value = IntermediateFormat::Convert(left);
const auto right_value = IntermediateFormat::Convert(right);
const auto value = static_cast<Intermediate>(left_value.value - right_value.value);
return Value<IntermediateFormat>{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) {
// Select the intermediate precision based on the argument format with
// the largest precision. The intermediate format precision is
// guaranteed to be sufficient for multiplication with itself or
// smaller, unless the type is already maximally wide. It is up to the
// user to ensure that multiplication does not exceed the maximum
// supported precision.
using Integer = std::conditional_t<LeftFormat::Bits >= RightFormat::Bits,
typename LeftFormat::Intermediate,
typename RightFormat::Intermediate>;
const size_t FractionalBits = LeftFormat::FractionalBits + RightFormat::FractionalBits;
using IntermediateFormat = FixedFormat<Integer, FractionalBits>;
using Intermediate = typename IntermediateFormat::Intermediate;
const Integer left_value = left.value;
const Integer right_value = right.value;
const auto value = static_cast<Intermediate>(left_value * right_value);
return Value<IntermediateFormat>{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) {
// Select the intermediate precision based on the format with the
// largest precision. The intermediate format precision is guaranteed
// to be sufficient for division with itself or smaller, unless the type
// is already maximally wide. It is up to the user to ensure that
// division does not exceed the maximum supported precision.
using LeftOrRightFormat = std::conditional_t<LeftFormat::Bits >= RightFormat::Bits,
LeftFormat, RightFormat>;
using Intermediate = std::conditional_t<LeftOrRightFormat::Bits >= TargetFormat::Bits,
typename LeftOrRightFormat::Intermediate,
typename TargetFormat::Intermediate>;
const size_t FractionalBits = TargetFormat::FractionalBits + RightFormat::FractionalBits;
using IntermediateFormat = FixedFormat<Intermediate, FractionalBits>;
using QuotientFormat = FixedFormat<Intermediate, TargetFormat::FractionalBits>;
const auto left_value = IntermediateFormat::Convert(left).value;
return Value<QuotientFormat>{left_value / right.value};
}
};
// 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
// the format with the least resolution before comparison.
template <typename LeftInteger, size_t LeftFractionalBits,
typename RightInteger, size_t RightFractionalBits>
struct ComparisonTraits<Fixed<LeftInteger, LeftFractionalBits>,
Fixed<RightInteger, RightFractionalBits>> : std::true_type {
static constexpr auto Left(Fixed<LeftInteger, LeftFractionalBits> value) {
if constexpr (LeftFractionalBits <= RightFractionalBits) {
return value;
} else {
using Format = typename Fixed<RightInteger, RightFractionalBits>::Format;
return Format::Convert(value.value());
}
}
static constexpr auto Right(Fixed<RightInteger, RightFractionalBits> value) {
if constexpr (LeftFractionalBits > RightFractionalBits) {
return value;
} else {
using Format = typename Fixed<LeftInteger, LeftFractionalBits>::Format;
return 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 Right(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. The Fixed is converted to
// integer format before comparison.
template <typename Integer, size_t FractionalBits, typename T>
struct ComparisonTraits<Fixed<Integer, FractionalBits>, T, std::enable_if_t<std::is_integral_v<T>>>
: std::true_type {
using TargetType = Fixed<T, 0>;
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. The Fixed is converted to
// integer format before comparison.
template <typename Integer, size_t FractionalBits, typename T>
struct ComparisonTraits<T, Fixed<Integer, FractionalBits>, std::enable_if_t<std::is_integral_v<T>>>
: std::true_type {
using TargetType = Fixed<T, 0>;
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