blob: 783a478f1a9f0fddaada506bd0fecd6d95c48b83 [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
#ifndef FFL_FIXED_FORMAT_H_
#define FFL_FIXED_FORMAT_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <ffl/utility.h>
namespace ffl {
// Forward declaration.
template <typename Integer, size_t FractionalBits>
struct FixedFormat;
// Type representing an intermediate value of a given FixedFormat.
template <typename>
struct Value;
template <typename Integer, size_t FractionalBits>
struct Value<FixedFormat<Integer, FractionalBits>> {
using Format = FixedFormat<Integer, FractionalBits>;
using Intermediate = typename Format::Intermediate;
explicit constexpr Value(Intermediate value) : value{value} {}
const Intermediate value;
};
// Predicate to determine whether the given integer type and number of
// fractional bits is valid.
template <typename Integer, size_t FractionalBits>
static constexpr bool FormatIsValid = (std::is_signed_v<Integer> &&
FractionalBits < sizeof(Integer) * 8) ||
(std::is_unsigned_v<Integer> &&
FractionalBits <= sizeof(Integer) * 8);
// Type representing the format of a fixed-point value in terms of the
// underlying integer type and fractional precision. Provides key constants and
// operations for fixed-point computation and format manipulation.
template <typename Integer_, size_t FractionalBits_>
struct FixedFormat {
static_assert(std::is_integral_v<Integer_>,
"The Integer template parameter must be an integral type!");
static_assert(FormatIsValid<Integer_, FractionalBits_>,
"The number of fractional bits must fit within the positive bits!");
// The underlying integral type of the fixed-point values in this format.
using Integer = Integer_;
// The intermediate integral type used by computations in this format.
using Intermediate = typename IntermediateType<Integer>::Type;
// Indicates whether the underlying integer is singed or unsigned.
static constexpr bool IsSigned = std::is_signed_v<Integer>;
static constexpr bool IsUnsigned = std::is_unsigned_v<Integer>;
// Numeric constants for fixed-point computations.
static constexpr size_t Bits = sizeof(Integer) * 8;
static constexpr size_t IntermediateBits = sizeof(Intermediate) * 8;
static constexpr size_t FractionalBits = FractionalBits_;
static constexpr size_t IntegralBits = Bits - FractionalBits;
static constexpr size_t Power = 1 << FractionalBits;
// Indicates whether positive one can only be represented fractionally.
static constexpr bool ApproximateUnit =
(IsSigned && FractionalBits == Bits - 1) || FractionalBits == Bits;
static constexpr Integer One = 1; // Typed constant used in shifts below.
static constexpr Integer FractionalMask = Power - 1;
static constexpr Integer IntegralMask = ~FractionalMask;
static constexpr Integer SignBit = IsSigned ? One << (Bits - 1) : 0;
static constexpr Integer BinaryPoint = FractionalBits > 0 ? One << (FractionalBits - 1) : 0;
static constexpr Integer OnesPlace = One << FractionalBits;
static constexpr Integer Min = std::numeric_limits<Integer>::min();
static constexpr Integer Max = std::numeric_limits<Integer>::max();
static constexpr Integer IntegralMin = static_cast<Integer>(Min / Power);
static constexpr Integer IntegralMax = static_cast<Integer>(Max / Power);
// Trivially converts from Integer to Intermediate type.
static constexpr Intermediate ToIntermediate(Intermediate value) { return value; }
// Saturates an intermediate value to the valid range of the base type.
template <typename I, typename = std::enable_if_t<std::is_integral_v<I>>>
static constexpr Integer Saturate(I value) {
return ClampCast<Integer>(value);
}
static constexpr Integer Saturate(Value<FixedFormat> value) { return Saturate(value.value); }
// Rounds |value| to the given significant bit |Place| using the convergent,
// or round-half-to-even, method to eliminate positive/negative and
// towards/away from zero biases. This is the default rounding mode used in
// IEEE 754 computing functions and operators.
//
// References:
// https://en.wikipedia.org/wiki/Rounding#Round_half_to_even
// https://en.wikipedia.org/wiki/Nearest_integer_function
//
// Optimization Analysis:
// https://godbolt.org/z/Cozc9r
//
// For example, rounding an 8bit value to bit 4 produces these values in the
// constants defined below:
//
// uint8_t value = vvvphmmm
//
// PlaceBit = 00010000 -> 000p0000
// PlaceMask = 11110000 -> vvvp0000
// HalfBit = 00001000 -> 0000h000
// HalfMask = 00000111 -> 00000mmm
// PlaceShift = 2
//
// Rounding half to even is computed as follows:
//
// PlaceBit = 00010000
// value = vvvvvvvv
// & -------------------
// 000p0000
// PlaceShift 2
// >> -------------------
// odd_bit 00000p00
// HalfMask 00000111
// value vvvvvvvv
// + -------------------
// rrrrxxxx
// PlaceMask 11110000
// & -------------------
// rounded rrrr0000
//
template <size_t Place>
static constexpr Intermediate Round(Intermediate value, Bit<Place>) {
using Unsigned = std::make_unsigned_t<Intermediate>;
// Bit of the significant figure to round to and mask of the significant
// bits after rounding.
const Unsigned PlaceBit = Unsigned{1} << Place;
const Unsigned PlaceMask = ~(PlaceBit - 1);
// Bit representing one half of the significant figure to round to
// and mask of the bits below it, if any.
const Unsigned HalfBit = Unsigned{1} << (Place - 1);
const Unsigned HalfMask = Place > 1 ? HalfBit - 1 : 0;
// Shift representing where to add the odd bit when rounding to even.
const size_t PlaceShift = Place > 1 ? 2 : 1;
// 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. This optimizes out when |value| is unsigned.
const Unsigned mask = static_cast<Unsigned>(-(value < 0));
const Unsigned one = mask & 1;
// Compute the absolute value of |value| using two's complement. This
// optimizes out when |value| is unsigned.
const auto absolute = (static_cast<Unsigned>(value) ^ mask) + one;
// Round half to even.
const Unsigned odd_bit = (absolute & PlaceBit) >> PlaceShift;
Intermediate rounded = 0;
// All values are positive, catch positive overflow and saturate.
if (__builtin_add_overflow(absolute, HalfMask + odd_bit, &rounded)) {
rounded = std::numeric_limits<Intermediate>::max();
} else {
rounded &= PlaceMask;
}
// Restore original sign. This optimizes out when |value| is unsigned.
return static_cast<Intermediate>((static_cast<Unsigned>(rounded) ^ mask) + one);
}
// Rounding to the 0th bit is a no-op.
static constexpr Intermediate Round(Intermediate value, Bit<0>) { return value; }
// Rounds the intermediate |value| around the integer position.
static constexpr Intermediate Round(Intermediate value) {
return Round(value, ToPlace<FractionalBits>);
}
// Converts an intermediate value in SourceFormat to this format, rounding
// as necessary.
template <typename SourceFormat>
static constexpr Value<FixedFormat> Convert(Value<SourceFormat> value) {
using LargestFormat =
std::conditional_t<(SourceFormat::Bits > Bits), SourceFormat, FixedFormat>;
using LargestInteger = MatchSignedOrUnsigned<Integer, typename LargestFormat::Integer>;
using IntermediateFormat = FixedFormat<LargestInteger, SourceFormat::FractionalBits>;
using ValueType = typename IntermediateFormat::Intermediate;
const ValueType clamped_value = IntermediateFormat::Saturate(value.value);
if constexpr (SourceFormat::FractionalBits >= FractionalBits) {
const size_t delta = SourceFormat::FractionalBits - FractionalBits;
const ValueType power = ValueType{1} << delta;
const ValueType converted_value =
IntermediateFormat::Round(clamped_value, ToPlace<delta>) / power;
return Value<FixedFormat>{static_cast<Intermediate>(converted_value)};
} else {
const size_t delta = FractionalBits - SourceFormat::FractionalBits;
const ValueType power = ValueType{1} << delta;
const ValueType converted_value = static_cast<ValueType>(clamped_value * power);
return Value<FixedFormat>{static_cast<Intermediate>(converted_value)};
}
}
// Converting to the same format is a no-op.
static constexpr Value<FixedFormat> Convert(Value<FixedFormat> value) { return value; }
};
} // namespace ffl
#endif // FFL_FIXED_FORMAT_H_