Skip to content

Commit 3485d9a

Browse files
committed
constexpr arithmetic operators
Solves part of MikeLankamp#71
1 parent 05d9051 commit 3485d9a

File tree

5 files changed

+55
-14
lines changed

5 files changed

+55
-14
lines changed

include/fpm/fixed.hpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include <type_traits>
1010

1111
#ifndef FPM_NODISCARD
12-
# if __cplusplus >= 201703L
12+
# if __cplusplus >= 201703L /* C++17 */
1313
# define FPM_NODISCARD [[nodiscard]]
1414
# else
1515
# define FPM_NODISCARD
@@ -33,12 +33,14 @@ class fixed
3333
static_assert(sizeof(IntermediateType) > sizeof(BaseType), "IntermediateType must be larger than BaseType");
3434
static_assert(std::numeric_limits<IntermediateType>::is_signed == std::numeric_limits<BaseType>::is_signed, "IntermediateType must have same signedness as BaseType");
3535

36+
public:
3637
/// Although this value fits in the BaseType in terms of bits, if there's only one integral bit, this value
3738
/// is incorrect (flips from positive to negative), so we must extend the size to IntermediateType.
3839
static constexpr IntermediateType FRACTION_MULT = IntermediateType(1) << FractionBits;
3940

4041
#pragma region Constructors
4142

43+
private:
4244
struct raw_construct_tag {};
4345
constexpr inline fixed(BaseType val, raw_construct_tag) noexcept : m_value(val) {}
4446

@@ -154,6 +156,8 @@ class fixed
154156
return m_value != 0;
155157
}
156158

159+
#pragma region Addition
160+
157161
inline fixed& operator+=(const fixed& y) noexcept
158162
{
159163
m_value += y.m_value;
@@ -167,6 +171,10 @@ class fixed
167171
return *this;
168172
}
169173

174+
#pragma endregion
175+
176+
#pragma region Subtraction
177+
170178
inline fixed& operator-=(const fixed& y) noexcept
171179
{
172180
m_value -= y.m_value;
@@ -180,6 +188,10 @@ class fixed
180188
return *this;
181189
}
182190

191+
#pragma endregion
192+
193+
#pragma region Multiplication
194+
183195
inline fixed& operator*=(const fixed& y) noexcept
184196
{
185197
if (EnableRounding){
@@ -202,6 +214,10 @@ class fixed
202214
return *this;
203215
}
204216

217+
#pragma endregion
218+
219+
#pragma region Division
220+
205221
inline fixed& operator/=(const fixed& y) noexcept
206222
{
207223
assert(y.m_value != 0);
@@ -227,6 +243,8 @@ class fixed
227243

228244
#pragma endregion
229245

246+
#pragma endregion
247+
230248
private:
231249
BaseType m_value;
232250
};
@@ -241,6 +259,8 @@ using fixed_8_24 = fixed<std::int32_t, std::int64_t, 24>;
241259

242260
#pragma endregion
243261

262+
#pragma region Arithmetic operators
263+
244264
#pragma region Addition
245265

246266
template <typename B, typename I, unsigned int F, bool R>
@@ -290,19 +310,28 @@ FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator-(T x, const fixed<B, I
290310
template <typename B, typename I, unsigned int F, bool R>
291311
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator*(const fixed<B, I, F, R>& x, const fixed<B, I, F, R>& y) noexcept
292312
{
293-
return fixed<B, I, F, R>(x) *= y;
313+
if (R){
314+
// Normal fixed-point multiplication is: x * y / 2**FractionBits.
315+
// To correctly round the last bit in the result, we need one more bit of information.
316+
// We do this by multiplying by two before dividing and adding the LSB to the real result.
317+
const auto value = (static_cast<I>(x.raw_value()) * y.raw_value()) / (fixed<B, I, F, R>::FRACTION_MULT / 2);
318+
return fixed<B, I, F, R>::from_raw_value(static_cast<B>((value / 2) + (value % 2)));
319+
} else {
320+
const auto value = (static_cast<I>(x.raw_value()) * y.raw_value()) / fixed<B, I, F, R>::FRACTION_MULT;
321+
return fixed<B, I, F, R>::from_raw_value(static_cast<B>(value));
322+
}
294323
}
295324

296325
template <typename B, typename I, unsigned int F, bool R, typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
297326
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator*(const fixed<B, I, F, R>& x, T y) noexcept
298327
{
299-
return fixed<B, I, F, R>(x) *= y;
328+
return fixed<B, I, F, R>::from_raw_value(x.raw_value() * y);
300329
}
301330

302331
template <typename B, typename I, unsigned int F, bool R, typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
303332
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator*(T x, const fixed<B, I, F, R>& y) noexcept
304333
{
305-
return fixed<B, I, F, R>(y) *= x;
334+
return fixed<B, I, F, R>::from_raw_value(x * y.raw_value());
306335
}
307336
#pragma endregion
308337

@@ -311,23 +340,35 @@ FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator*(T x, const fixed<B, I
311340
template <typename B, typename I, unsigned int F, bool R>
312341
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator/(const fixed<B, I, F, R>& x, const fixed<B, I, F, R>& y) noexcept
313342
{
314-
return fixed<B, I, F, R>(x) /= y;
343+
assert(y.raw_value() != 0);
344+
if (R){
345+
// Normal fixed-point division is: x * 2**FractionBits / y.
346+
// To correctly round the last bit in the result, we need one more bit of information.
347+
// We do this by multiplying by two before dividing and adding the LSB to the real result.
348+
const auto value = (static_cast<I>(x.raw_value()) * fixed<B, I, F, R>::FRACTION_MULT * 2) / y.raw_value();
349+
return fixed<B, I, F, R>::from_raw_value(static_cast<B>((value / 2) + (value % 2)));
350+
} else {
351+
const auto value = (static_cast<I>(x.raw_value()) * fixed<B, I, F, R>::FRACTION_MULT) / y.raw_value();
352+
return fixed<B, I, F, R>::from_raw_value(static_cast<B>(value));
353+
}
315354
}
316355

317356
template <typename B, typename I, unsigned int F, typename T, bool R, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
318357
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator/(const fixed<B, I, F, R>& x, T y) noexcept
319358
{
320-
return fixed<B, I, F, R>(x) /= y;
359+
return fixed<B, I, F, R>::from_raw_value(x.raw_value() / y);
321360
}
322361

323362
template <typename B, typename I, unsigned int F, typename T, bool R, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
324363
FPM_NODISCARD constexpr inline fixed<B, I, F, R> operator/(T x, const fixed<B, I, F, R>& y) noexcept
325364
{
326-
return fixed<B, I, F, R>(x) /= y;
365+
return fixed<B, I, F, R>(x) / y;
327366
}
328367

329368
#pragma endregion
330369

370+
#pragma endregion
371+
331372
#pragma region Comparison operators
332373

333374
template <typename B, typename I, unsigned int F, bool R>
@@ -348,7 +389,7 @@ FPM_NODISCARD constexpr inline bool operator==(const T x, const fixed<B, I, F, R
348389
return fixed<B, I, F, R>{x} == y;
349390
}
350391

351-
#if __cplusplus >= 202002L
392+
#if __cplusplus >= 202002L /* C++20 */
352393

353394
template <typename B, typename I, unsigned int F, bool R>
354395
FPM_NODISCARD constexpr inline auto operator<=>(const fixed<B, I, F, R>& x, const fixed<B, I, F, R>& y) noexcept
@@ -550,7 +591,7 @@ struct is_fixed : std::false_type {};
550591
template<typename BaseType, typename IntermediateType, unsigned int FractionBits, bool EnableRounding>
551592
struct is_fixed<fixed<BaseType, IntermediateType, FractionBits, EnableRounding>> : std::true_type {};
552593

553-
#if __cplusplus >= 201703L
594+
#if __cplusplus >= 201703L /* C++17 */
554595
template<typename T>
555596
inline constexpr bool is_fixed_v = is_fixed<T>::value;
556597
#endif

include/fpm/ios.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ std::basic_istream<CharT, Traits>& operator>>(std::basic_istream<CharT, Traits>&
737737

738738
}
739739

740-
#if __cplusplus >= 202002L
740+
#if __cplusplus >= 202002L /* C++20 */
741741
# include <version>
742742
# ifdef __cpp_lib_format
743743
# include <format>

tests/constexpr.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "common.hpp"
22

3+
#if __cplusplus >= 201402L /* C++14 */
34
using P = fpm::fixed_16_16;
45

56
TEST(constexpr, addition)
@@ -38,7 +39,6 @@ TEST(constexpr, subtraction)
3839
static_assert(1 - P{2} == P{-1}, "Arithmetics failed");
3940
}
4041

41-
/*
4242
TEST(constexpr, multiplication)
4343
{
4444
static_assert(P{0} * P{0} == P{0}, "Arithmetics failed");
@@ -68,4 +68,4 @@ TEST(constexpr, division)
6868
static_assert(1 / P{1} == P{1 }, "Arithmetics failed");
6969
static_assert(1 / P{2} == P{0.5}, "Arithmetics failed");
7070
}
71-
*/
71+
#endif

tests/formatting.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "common.hpp"
22
#include <fpm/ios.hpp>
33

4-
#if __cplusplus >= 202002L
4+
#if __cplusplus >= 202002L /* C++20 */
55
# include <version>
66
# ifdef __cpp_lib_format
77
# include <format>

tests/formatting_wchar.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "common.hpp"
22
#include <fpm/ios.hpp>
33

4-
#if __cplusplus >= 202002L
4+
#if __cplusplus >= 202002L /* C++20 */
55
# include <version>
66
# ifdef __cpp_lib_format
77
# include <format>

0 commit comments

Comments
 (0)