-
Notifications
You must be signed in to change notification settings - Fork 0
Support custom fixed point data type #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MitchellThompkins
wants to merge
23
commits into
main
Choose a base branch
from
support-fixed-point
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
c7cb5ae
Add FixedPoint scalar type for freestanding fixed-point filter support
MitchellThompkins b250ad5
Add FixedFilter and fixed-point converting constructor
MitchellThompkins e4ac5b0
Add fixed_point tests
MitchellThompkins 7978150
Add fixed_filter integration tests
MitchellThompkins d886073
Add fixed_point and fixed_filter to umbrella include
MitchellThompkins 5f96f48
Fix T{} zero-init and add saturation to FixedPoint double constructor
MitchellThompkins 4c5dce3
Add FixedButterworth, FixedElliptic, and common Q-format aliases
MitchellThompkins e9f737b
Update fixed_filter tests to use FixedButterworth and built-in Q aliases
MitchellThompkins 9c8fde4
Address PR review: {} init syntax, operator/ precondition docs
MitchellThompkins f5902ad
Add fixed point filter documentation
MitchellThompkins 6c8e91e
Fix signed overflow UB in FixedPoint add/sub/negate
MitchellThompkins a051f31
Round to nearest in FixedPoint multiply
MitchellThompkins 8546f77
Fix Q-format naming in FixedPoint comments
MitchellThompkins aee5fd4
Document a[0]=1 saturation and coeffs_b() range check
MitchellThompkins ef5a5c5
Document ambiguous FixedPoint integer construction
MitchellThompkins 84d20a9
Brace all loop bodies in fixed_filter.test.cpp
MitchellThompkins 9136cbd
Add UBSan CI job
MitchellThompkins 67b0b14
Use range-based for loops to fill test arrays
MitchellThompkins 5c6d868
Fix misleading FixedPoint disambiguation example in docs
MitchellThompkins 3c3dd54
Drop operator/ from custom scalar requirements
MitchellThompkins c4a2445
Document the fixed-point test layer in verification.md
MitchellThompkins 99e6572
Saturate FixedPoint arithmetic on overflow instead of wrapping
MitchellThompkins debab9d
Finish fixed-point docs and add long tier support
MitchellThompkins File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #ifndef CONSTFILT_FIXED_FILTER_HPP | ||
| #define CONSTFILT_FIXED_FILTER_HPP | ||
|
|
||
| #include "filter.hpp" | ||
|
|
||
| namespace constfilt | ||
| { | ||
|
|
||
| // Concrete fixed-point filter. Converts coefficients from any floating-point | ||
| // Filter<U, NB, NA> to a stored fixed-point representation at construction. | ||
| // All filtering operations are inherited from Filter and run in TFixed | ||
| // arithmetic. | ||
| // | ||
| // Typical use: | ||
| // constexpr Butterworth<double, 4> proto(100.0, 1000.0); | ||
| // constexpr FixedFilter<FixedPoint<short, int, 15>, 5, 5> filt(proto); | ||
| template <typename TFixed, consteig::Size NB, consteig::Size NA> | ||
| class FixedFilter : public Filter<TFixed, NB, NA> | ||
| { | ||
| public: | ||
| template <typename U> | ||
| constexpr explicit FixedFilter(const Filter<U, NB, NA> &src) | ||
| : Filter<TFixed, NB, NA>(src) | ||
| { | ||
| } | ||
| }; | ||
|
|
||
| } // namespace constfilt | ||
|
|
||
| #endif // CONSTFILT_FIXED_FILTER_HPP |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| #ifndef CONSTFILT_FIXED_POINT_HPP | ||
| #define CONSTFILT_FIXED_POINT_HPP | ||
|
|
||
| namespace constfilt | ||
| { | ||
|
|
||
| namespace detail | ||
| { | ||
|
|
||
| template <typename T> struct make_unsigned; | ||
|
|
||
| template <> struct make_unsigned<signed char> | ||
| { | ||
| using type = unsigned char; | ||
| }; | ||
|
|
||
| template <> struct make_unsigned<short> | ||
| { | ||
| using type = unsigned short; | ||
| }; | ||
|
|
||
| template <> struct make_unsigned<int> | ||
| { | ||
| using type = unsigned int; | ||
| }; | ||
|
|
||
| // long long / __int128 tier: only available with compiler extension support. | ||
| // TWider = __int128 is required when TInt = long long. | ||
| template <> struct make_unsigned<long long> | ||
| { | ||
| using type = unsigned long long; | ||
| }; | ||
|
|
||
| } // namespace detail | ||
|
|
||
| // Fixed-point scalar type. | ||
| // | ||
| // Template parameters: | ||
| // TInt - signed integer storage type (signed char, short, int, long long) | ||
| // TWider - signed integer type strictly wider than TInt, used as multiply | ||
| // intermediate to prevent overflow (e.g. short->int, int->long | ||
| // long) | ||
| // FracBits - number of fractional bits; must satisfy 0 < FracBits < | ||
| // 8*sizeof(TInt) | ||
| // | ||
| // Supported tiers (without compiler extensions): | ||
| // FixedPoint<signed char, short, 7> Q1.7 | ||
| // FixedPoint<short, int, 15> Q1.15 | ||
| // FixedPoint<int, long long, 31> Q1.31 | ||
| // | ||
| // Overflow on construction and all arithmetic wraps silently (two's | ||
| // complement). The right-shift in operator* is arithmetic; this is | ||
| // implementation-defined in C++17 but holds on all supported targets (GCC/Clang | ||
| // with two's complement). | ||
| template <typename TInt, typename TWider, unsigned FracBits> class FixedPoint | ||
| { | ||
| static_assert(sizeof(TInt) < sizeof(TWider), | ||
| "TWider must be strictly wider than TInt"); | ||
| static_assert(TInt(-1) < TInt(0), "TInt must be a signed type"); | ||
| static_assert(TWider(-1) < TWider(0), "TWider must be a signed type"); | ||
| static_assert(FracBits > 0u, "FracBits must be greater than zero"); | ||
| static_assert(FracBits < sizeof(TInt) * 8u, | ||
| "FracBits must be less than the bit width of TInt"); | ||
|
|
||
| using UInt = typename detail::make_unsigned<TInt>::type; | ||
|
|
||
| static constexpr TWider SCALE = TWider(1) << FracBits; | ||
| static constexpr TInt INT_MAX_VAL = | ||
| static_cast<TInt>(static_cast<UInt>(-1) >> 1); | ||
| static constexpr TInt INT_MIN_VAL = static_cast<TInt>(-INT_MAX_VAL - 1); | ||
|
|
||
| TInt _raw; | ||
|
|
||
| struct raw_tag | ||
| { | ||
| }; | ||
| constexpr FixedPoint(TInt val, raw_tag) noexcept : _raw(val) | ||
| { | ||
| } | ||
|
|
||
| public: | ||
| constexpr FixedPoint() noexcept : _raw(0) | ||
| { | ||
| } | ||
|
|
||
| // Construct from double. Rounds to nearest (ties away from zero). | ||
| // Saturates at INT_MAX_VAL / INT_MIN_VAL if v is outside the representable | ||
| // range. Saturation avoids undefined behavior from out-of-range | ||
| // float-to-int casts in constexpr contexts. | ||
| constexpr explicit FixedPoint(double v) noexcept : _raw(to_raw(v)) | ||
| { | ||
| } | ||
|
|
||
| private: | ||
| static constexpr TInt to_raw(double v) noexcept | ||
| { | ||
| const double scaled = v >= 0.0 ? v * static_cast<double>(SCALE) + 0.5 | ||
| : v * static_cast<double>(SCALE) - 0.5; | ||
| return scaled >= static_cast<double>(INT_MAX_VAL) | ||
| ? INT_MAX_VAL | ||
| : (scaled <= static_cast<double>(INT_MIN_VAL) | ||
| ? INT_MIN_VAL | ||
| : static_cast<TInt>(scaled)); | ||
| } | ||
|
|
||
| public: | ||
| // Construct from integer. The value is scaled by 2^FracBits. | ||
| // Wraps silently if integer_val is outside the representable range. | ||
| constexpr explicit FixedPoint(TInt integer_val) noexcept | ||
| : _raw(static_cast<TInt>(static_cast<TWider>(integer_val) * SCALE)) | ||
| { | ||
| } | ||
|
|
||
| constexpr TInt raw_value() const noexcept | ||
| { | ||
| return _raw; | ||
| } | ||
|
|
||
| static constexpr FixedPoint from_raw(TInt val) noexcept | ||
| { | ||
| return {val, raw_tag{}}; | ||
| } | ||
|
|
||
| constexpr double to_double() const noexcept | ||
| { | ||
| return static_cast<double>(_raw) / static_cast<double>(SCALE); | ||
| } | ||
|
|
||
| // Arithmetic | ||
| constexpr FixedPoint operator+(FixedPoint rhs) const noexcept | ||
| { | ||
| return {static_cast<TInt>(_raw + rhs._raw), raw_tag{}}; | ||
| } | ||
| constexpr FixedPoint operator-(FixedPoint rhs) const noexcept | ||
| { | ||
| return {static_cast<TInt>(_raw - rhs._raw), raw_tag{}}; | ||
| } | ||
| constexpr FixedPoint operator-() const noexcept | ||
| { | ||
| return {static_cast<TInt>(-_raw), raw_tag{}}; | ||
| } | ||
| constexpr FixedPoint operator*(FixedPoint rhs) const noexcept | ||
| { | ||
| return {static_cast<TInt>((static_cast<TWider>(_raw) * | ||
| static_cast<TWider>(rhs._raw)) >> | ||
| FracBits), | ||
| raw_tag{}}; | ||
| } | ||
| constexpr FixedPoint operator/(FixedPoint rhs) const noexcept | ||
| { | ||
| return {static_cast<TInt>((static_cast<TWider>(_raw) * SCALE) / | ||
| static_cast<TWider>(rhs._raw)), | ||
| raw_tag{}}; | ||
| } | ||
|
|
||
| // Compound assignment | ||
| constexpr FixedPoint &operator+=(FixedPoint rhs) noexcept | ||
| { | ||
| _raw += rhs._raw; | ||
| return *this; | ||
| } | ||
| constexpr FixedPoint &operator-=(FixedPoint rhs) noexcept | ||
| { | ||
| _raw -= rhs._raw; | ||
| return *this; | ||
| } | ||
| constexpr FixedPoint &operator*=(FixedPoint rhs) noexcept | ||
| { | ||
| return *this = *this * rhs; | ||
| } | ||
| constexpr FixedPoint &operator/=(FixedPoint rhs) noexcept | ||
| { | ||
| return *this = *this / rhs; | ||
| } | ||
|
|
||
| // Comparison | ||
| constexpr bool operator==(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw == rhs._raw; | ||
| } | ||
| constexpr bool operator!=(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw != rhs._raw; | ||
| } | ||
| constexpr bool operator<(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw < rhs._raw; | ||
| } | ||
| constexpr bool operator>(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw > rhs._raw; | ||
| } | ||
| constexpr bool operator<=(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw <= rhs._raw; | ||
| } | ||
| constexpr bool operator>=(FixedPoint rhs) const noexcept | ||
| { | ||
| return _raw >= rhs._raw; | ||
| } | ||
| }; | ||
|
|
||
| // Numeric limits for FixedPoint. Replaces std::numeric_limits for freestanding | ||
| // targets. min/max are computed via unsigned bit manipulation (no stdlib | ||
| // required). | ||
| template <typename TInt, typename TWider, unsigned FracBits> struct fixed_limits | ||
| { | ||
| using F = FixedPoint<TInt, TWider, FracBits>; | ||
| using UInt = typename detail::make_unsigned<TInt>::type; | ||
|
|
||
| static constexpr TInt int_max = | ||
| static_cast<TInt>(static_cast<UInt>(-1) >> 1); | ||
| static constexpr TInt int_min = static_cast<TInt>(-int_max - 1); | ||
|
|
||
| static constexpr F min() noexcept | ||
| { | ||
| return F::from_raw(int_min); | ||
| } | ||
| static constexpr F max() noexcept | ||
| { | ||
| return F::from_raw(int_max); | ||
| } | ||
| static constexpr F lowest() noexcept | ||
| { | ||
| return min(); | ||
| } | ||
| static constexpr F epsilon() noexcept | ||
| { | ||
| return F::from_raw(1); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace constfilt | ||
|
|
||
| #endif // CONSTFILT_FIXED_POINT_HPP | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.