diff --git a/ext/gcc/modm_atomic.hpp.in b/ext/gcc/modm_atomic.hpp.in index 7c0bfdf9c2..b9c8bce4c2 100644 --- a/ext/gcc/modm_atomic.hpp.in +++ b/ext/gcc/modm_atomic.hpp.in @@ -153,6 +153,7 @@ __atomic_compare_exchange_{{len//8}}(volatile void *ptr, void *expected, {{len|u %% endmacro // ================================ lock free ================================= +#ifndef __clang__ extern "C" [[gnu::always_inline]] inline bool __atomic_is_lock_free (unsigned int object_size, const volatile void *ptr) { @@ -161,7 +162,7 @@ __atomic_is_lock_free (unsigned int object_size, const volatile void *ptr) return ((uintptr_t)ptr & (object_size - 1)) == 0; return false; } - +#endif // __clang__ %% macro atomic_fetch(len) %% for name, op in [("and", "&"), ("or", "|"), ("xor", "^"), ("nand", "&")] diff --git a/src/modm/architecture/interface/interrupt.hpp b/src/modm/architecture/interface/interrupt.hpp index 77e13d0925..d654aecec8 100644 --- a/src/modm/architecture/interface/interrupt.hpp +++ b/src/modm/architecture/interface/interrupt.hpp @@ -129,11 +129,20 @@ # define MODM_ISR_CALL(vector) \ MODM_ISR_VALIDATE(#vector, vector); \ vector ## _IRQHandler() + +#ifdef MODM_COMPILER_CLANG +# define MODM_ISR(vector, ...) \ + MODM_ISR_VALIDATE(#vector, vector); \ + modm_extern_c void vector ## _IRQHandler(void) \ + __attribute__((used)) __VA_ARGS__; \ + void vector ## _IRQHandler(void) +#else # define MODM_ISR(vector, ...) \ MODM_ISR_VALIDATE(#vector, vector); \ modm_extern_c void vector ## _IRQHandler(void) \ __attribute__((externally_visible)) __VA_ARGS__; \ void vector ## _IRQHandler(void) +#endif // MODM_COMPILER_CLANG #else diff --git a/src/modm/io/iostream.hpp.in b/src/modm/io/iostream.hpp.in index aaf251abac..dc21a4eefc 100644 --- a/src/modm/io/iostream.hpp.in +++ b/src/modm/io/iostream.hpp.in @@ -192,6 +192,15 @@ public: %% endif %% if core.startswith("cortex-m") +#ifdef __clang__ + // For ARM 'int32_t' is of type 'int'. Therefore there is no + // function here for the default type 'long'. As 'long' has the same + // width as 'int32_t' we just use a typedef here. + inline IOStream& operator << (const long& v) + { writeIntegerMode(static_cast(v)); return *this; } + inline IOStream& operator << (const unsigned long& v) + { writeIntegerMode(static_cast(v)); return *this; } +#else // For ARM 'int32_t' is of type 'long'. Therefore there is no // function here for the default type 'int'. As 'int' has the same // width as 'int32_t' we just use a typedef here. @@ -199,6 +208,7 @@ public: { writeIntegerMode(static_cast(v)); return *this; } inline IOStream& operator << (const unsigned int& v) { writeIntegerMode(static_cast(v)); return *this; } +#endif %% endif %% if options.with_float diff --git a/src/modm/math/utils/arithmetic_traits.hpp b/src/modm/math/utils/arithmetic_traits.hpp index bb66b01a9c..8071e64278 100644 --- a/src/modm/math/utils/arithmetic_traits.hpp +++ b/src/modm/math/utils/arithmetic_traits.hpp @@ -4,7 +4,7 @@ * Copyright (c) 2012, 2014, Niklas Hauser * Copyright (c) 2013, 2015, Sascha Schade * Copyright (c) 2015, Kevin Läufer - * Copyright (c) 2018, Christopher Durand + * Copyright (c) 2018, 2026, Christopher Durand * Copyright (c) 2022, Raphael Lehmann * * This file is part of the modm project. @@ -18,11 +18,14 @@ #ifndef MODM_ARITHMETIC_TRAITS_HPP #define MODM_ARITHMETIC_TRAITS_HPP +#include #include #include #include #include +#include + namespace modm { @@ -97,13 +100,9 @@ namespace detail struct WideType { using type = double; }; - template && !std::is_same_v, bool> - > > - using enable_if_int = T; - - template - struct WideType> + template + requires (!std::is_same_v, bool>) + struct WideType { static constexpr bool isNextIntLarger = std::numeric_limits::type>::max() > std::numeric_limits::max(); @@ -123,8 +122,9 @@ namespace detail using type = T; }; - template - struct MakeSigned> + template + requires (!std::is_same_v, bool>) + struct MakeSigned { using type = std::make_signed_t; }; @@ -135,8 +135,9 @@ namespace detail using type = T; }; - template - struct MakeUnsigned> + template + requires (!std::is_same_v, bool>) + struct MakeUnsigned { using type = std::make_unsigned_t; }; @@ -149,10 +150,10 @@ template using WideType = typename detail::WideType::type; template -using SignedType = typename detail::MakeSigned::type; +using SignedType [[deprecated("use std::make_signed_t")]] = typename detail::MakeSigned::type; // DEPRECATED: 2026q3 template -using UnsignedType = typename detail::MakeUnsigned::type; +using UnsignedType [[deprecated("use std::make_unsigned_t")]] = typename detail::MakeUnsigned::type; // DEPRECATED: 2026q3 /** * Arithmetic Traits @@ -176,13 +177,16 @@ using UnsignedType = typename detail::MakeUnsigned::type; * @endcode */ template -struct ArithmeticTraits +struct [[deprecated("use std::numeric_limits instead")]] ArithmeticTraits // DEPRECATED: 2026q3 { static constexpr bool isInteger = std::is_integral_v && !std::is_same_v, bool>; + // log10(2), not constexpr yet with clang + static constexpr auto log10_2 = 0.3010299956639812; + static constexpr unsigned char decimalDigits = - std::ceil(std::numeric_limits::digits * log10(2)) + (std::is_signed_v ? 1 : 0); + modm::ceil(std::numeric_limits::digits * log10_2) + (std::is_signed_v ? 1 : 0); }; /// @} diff --git a/src/modm/math/utils/cmath.hpp b/src/modm/math/utils/cmath.hpp new file mode 100644 index 0000000000..f46f4f2cf3 --- /dev/null +++ b/src/modm/math/utils/cmath.hpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2026, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +namespace modm::detail +{ + +template +consteval T +consteval_ceil(T value) +{ + static_assert(std::numeric_limits::is_iec559); + + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + if (value <= T{0}) { + // preserve sign of zero, e.g. ceil(-0.5f) = -0.0f + if (truncated == 0) { + return value * 0; + } + + return truncated; + } else { + return (value > truncated) ? (truncated + T{1}) : truncated; + } +} + +template +consteval T +consteval_floor(T value) +{ + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + // preserve sign of zero, floor(-0.0f) = -0.0f + if (value == T{0}) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + if (value >= T{0}) { + return truncated; + } else { + return (value < truncated) ? (truncated - T{1}) : truncated; + } +} + +template +consteval T +consteval_round(T value) +{ + // handle NaN + if (value != value) return value; + + constexpr auto mantissaDigits = std::numeric_limits::digits; + static_assert(std::numeric_limits::digits >= mantissaDigits); + + // calculate magnitude limit outside which all values must be integral + constexpr auto limit = T{1ll << (mantissaDigits - 1)}; + if (value >= limit || value <= -limit) { + return value; + } + + const auto truncated = static_cast(static_cast(value)); + const auto fraction = value - truncated; + + if (std::signbit(value)) { + if (fraction <= T{-0.5}) return truncated - T{1}; + if (truncated == 0) return value * T{0}; + return truncated; + } else { + if (fraction >= T{0.5}) return truncated + T{1}; + return truncated; + } +} + +} // namespace modm::detail + +namespace modm +{ + +template +constexpr T +ceil(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_ceil(value); + } else { + return std::ceil(value); + } +#else + return std::ceil(value); +#endif +} + +template +constexpr T +floor(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_floor(value); + } else { + return std::floor(value); + } +#else + return std::floor(value); +#endif +} + +template +constexpr T +round(T value) +{ +#ifdef MODM_COMPILER_CLANG + if consteval { + return detail::consteval_round(value); + } else { + return std::round(value); + } +#else + return std::round(value); +#endif +} + +} // namespace modm diff --git a/src/modm/platform/clock/sam/gclk_impl.hpp.in b/src/modm/platform/clock/sam/gclk_impl.hpp.in index 188a5958c7..88ac91193a 100644 --- a/src/modm/platform/clock/sam/gclk_impl.hpp.in +++ b/src/modm/platform/clock/sam/gclk_impl.hpp.in @@ -18,6 +18,8 @@ #include #include +#include + namespace modm::platform { extern "C" uint32_t SystemCoreClock; @@ -97,7 +99,7 @@ GenericClockController::enableDfll48mClosedLoop(uint32_t waitCycles) { static_assert(reference > 732_Hz, "DFLL48 reference frequency must be larger than 732 Hz"); static_assert(reference < 43_kHz, "DFLL48 reference frequency must be less than 33 kHz"); - constexpr auto multiplier = uint16_t(std::round(48_MHz / double(reference))); + constexpr auto multiplier = uint16_t(modm::round(48_MHz / double(reference))); %% if target.family == "d1x/d2x/dax" // Errata 1.2.1: Disable OnDemand mode @@ -389,7 +391,7 @@ findDpllConfig(double inputClock, double target, bool fractional) } // f_pll = f_reference * (1 + N_int + N_frac/(2^frac_bits)) const auto idealMultiplier = (target / inputClock) - 1; - const uint32_t multplier = std::min(maxMultiplier, std::round(idealMultiplier)); + const uint32_t multplier = std::min(maxMultiplier, modm::round(idealMultiplier)); if (fractional) { const auto output = inputClock * (multplier + 1) / (1u << DpllConfig::MultiplierFractionalBits); return DpllConfigCalculation { diff --git a/src/modm/platform/clock/sam/module.lb b/src/modm/platform/clock/sam/module.lb index a710bf3923..72ca21a074 100644 --- a/src/modm/platform/clock/sam/module.lb +++ b/src/modm/platform/clock/sam/module.lb @@ -20,7 +20,7 @@ def prepare(module, options): if not options[":target"].has_driver("gclk:sam"): return False - module.depends(":cmsis:device", ":architecture:delay", ":platform:clock") + module.depends(":cmsis:device", ":architecture:delay", ":math:utils", ":platform:clock") return True def build(env): diff --git a/src/modm/platform/core/cortex/delay_impl.hpp.in b/src/modm/platform/core/cortex/delay_impl.hpp.in index 2f711bccc6..33dfe106c7 100644 --- a/src/modm/platform/core/cortex/delay_impl.hpp.in +++ b/src/modm/platform/core/cortex/delay_impl.hpp.in @@ -22,6 +22,8 @@ #include %% endif +#include + /// @cond #define MODM_DELAY_NS_IS_ACCURATE 1 @@ -34,7 +36,7 @@ extern uint16_t delay_fcpu_MHz; constexpr uint8_t delay_fcpu_MHz_shift({{us_shift}}); constexpr uint16_t computeDelayMhz(uint32_t hz) -{ return std::round(hz / 1'000'000.f * (1ul << delay_fcpu_MHz_shift)); } +{ return modm::round(hz / 1'000'000.f * (1ul << delay_fcpu_MHz_shift)); } } modm_always_inline diff --git a/src/modm/platform/core/cortex/delay_ns.hpp.in b/src/modm/platform/core/cortex/delay_ns.hpp.in index c593088daa..746862b88d 100644 --- a/src/modm/platform/core/cortex/delay_ns.hpp.in +++ b/src/modm/platform/core/cortex/delay_ns.hpp.in @@ -10,7 +10,7 @@ // ---------------------------------------------------------------------------- #pragma once -#include +#include /// @cond namespace modm::platform @@ -21,7 +21,7 @@ void delay_ns(uint32_t ns); constexpr uint16_t computeDelayNsPerLoop(uint32_t hz) { - return std::round({{loop}}'000'000'000.0 / hz); + return modm::round({{loop}}'000'000'000.0 / hz); } } diff --git a/src/modm/platform/core/cortex/module.lb b/src/modm/platform/core/cortex/module.lb index 90a6678a2f..201007ea1a 100644 --- a/src/modm/platform/core/cortex/module.lb +++ b/src/modm/platform/core/cortex/module.lb @@ -227,6 +227,7 @@ def prepare(module, options): module.depends( ":architecture:interrupt", ":cmsis:device", + ":math:utils", ":stdc++") module.add_option( diff --git a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in index f306f43e1f..c06c6a80f0 100644 --- a/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in +++ b/src/modm/platform/dac/stm32/dac_dma_impl.hpp.in @@ -111,9 +111,9 @@ void DmaChannel::Priority priority) { %% if target.family in ["f2", "f4", "f7"] - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::template RequestMapping; %% else - using RequestMapping = typename DmaChannel::RequestMapping; + using RequestMapping = typename DmaChannel::template RequestMapping; %% endif constexpr auto request = RequestMapping::Request; diff --git a/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in b/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in index 28197a30f3..67a43f2825 100644 --- a/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in +++ b/src/modm/platform/i2c/at90_tiny_mega/i2c_master.hpp.in @@ -17,6 +17,7 @@ #include "i2c.hpp" #include +#include #include #include #include @@ -79,10 +80,10 @@ public: // calculate the fractional prescaler value constexpr float pre_part_raw = float(SystemClock::I2c) / ( 2 * baudrate ); - constexpr float pre_raw = std::floor(pre_part_raw) < 8 ? 0 : (pre_part_raw - 8) / pre; + constexpr float pre_raw = modm::floor(pre_part_raw) < 8 ? 0 : (pre_part_raw - 8) / pre; // respect the prescaler range of 0 to 255 - constexpr uint32_t pre_ceil = std::min(uint32_t(std::ceil(pre_raw)), 255ul); - constexpr uint32_t pre_floor = std::floor(pre_raw); + constexpr uint32_t pre_ceil = std::min(uint32_t(modm::ceil(pre_raw)), 255ul); + constexpr uint32_t pre_floor = modm::floor(pre_raw); // calculate the possible baudrates above and below the requested baudrate constexpr uint32_t baud_lower = SystemClock::I2c / ( 16 + 2 * pre_ceil * pre ); diff --git a/src/modm/platform/i2c/at90_tiny_mega/module.lb b/src/modm/platform/i2c/at90_tiny_mega/module.lb index 30179ffe4e..f9d6cc16fd 100644 --- a/src/modm/platform/i2c/at90_tiny_mega/module.lb +++ b/src/modm/platform/i2c/at90_tiny_mega/module.lb @@ -28,6 +28,7 @@ def prepare(module, options): ":architecture:accessor", ":architecture:i2c", ":architecture:interrupt", + ":math:utils", ":platform:gpio") return True diff --git a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in index 5fd43208b0..723f69b961 100644 --- a/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in +++ b/src/modm/platform/i2c/sam_x7x/i2c_master.hpp.in @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -74,7 +75,7 @@ private: // => HOLD = hold time * (peripheral clock) - 3 constexpr float holdTime = 300.e-9f; constexpr float holdIdeal = holdTime * clock - 3.f; - constexpr uint8_t hold = static_cast(std::clamp(std::ceil(holdIdeal), 0.f, 63.f)); + constexpr uint8_t hold = static_cast(std::clamp(modm::ceil(holdIdeal), 0.f, 63.f)); constexpr bool fastMode = baudrate > 125'000; // Baudrate threshold above which the low time is fixed to the minimum value of @@ -86,14 +87,14 @@ private: // t_low = ((CLDIV * 2^CKDIV) + 3) * (1 / peripheral clock) if constexpr (baudrate > minLowTimeLimit) { // calculate ideal low and high prescaler values (formula from ASF vendor HAL) - constexpr auto cldiv = uint32_t(std::round((minLowTime * clock) - 3)); + constexpr auto cldiv = uint32_t(modm::round((minLowTime * clock) - 3)); constexpr auto tHigh = 1.f / ((baudrate + (baudrate - minLowTimeLimit)) * 2.f); - constexpr auto chdiv = uint32_t(std::round((tHigh * clock) - 3)); + constexpr auto chdiv = uint32_t(modm::round((tHigh * clock) - 3)); // use 2^N pre-divider if max. prescaler exceeds 8 bits constexpr auto ckdiv = std::max(0, std::max(bits(cldiv), bits(chdiv)) - 8); - constexpr uint32_t cldivScaled = std::round(float(cldiv) / (1 << ckdiv)); - constexpr uint32_t chdivScaled = std::round(float(chdiv) / (1 << ckdiv)); + constexpr uint32_t cldivScaled = modm::round(float(cldiv) / (1 << ckdiv)); + constexpr uint32_t chdivScaled = modm::round(float(chdiv) / (1 << ckdiv)); if (!checkBaudrate()) return std::nullopt; @@ -101,11 +102,11 @@ private: return TWIHS_CWGR_HOLD(hold) | TWIHS_CWGR_CLDIV(cldivScaled) | TWIHS_CWGR_CHDIV(chdivScaled) | TWIHS_CWGR_CKDIV(ckdiv); } else { - constexpr auto div = uint32_t(std::round(clock / (baudrate * 2.f) - 3)); + constexpr auto div = uint32_t(modm::round(clock / (baudrate * 2.f) - 3)); // use 2^N pre-divider if max. prescaler exceeds 8 bits constexpr auto ckdiv = std::max(0, bits(div) - 8); - constexpr uint32_t divScaled = std::round(float(div) / (1 << ckdiv)); + constexpr uint32_t divScaled = modm::round(float(div) / (1 << ckdiv)); if (!checkBaudrate()) return std::nullopt; diff --git a/src/modm/platform/i2c/sam_x7x/module.lb b/src/modm/platform/i2c/sam_x7x/module.lb index 8c572e21e1..6e6ca9184a 100644 --- a/src/modm/platform/i2c/sam_x7x/module.lb +++ b/src/modm/platform/i2c/sam_x7x/module.lb @@ -63,6 +63,7 @@ def prepare(module, options): ":architecture:interrupt", ":cmsis:device", ":container", + ":math:utils", ":platform:gpio") for instance in listify(device.get_driver("twihs")["instance"]): diff --git a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp index be9bac7870..ea9c7d883f 100644 --- a/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp +++ b/src/modm/platform/i2c/stm32-extended/i2c_timing_calculator.hpp @@ -17,6 +17,8 @@ #include #include +#include + namespace modm { @@ -272,8 +274,8 @@ class I2cTimingCalculator ((1.0f / params.peripheralClock) - SyncTime) / clockPeriod - 1 ); - lowMinFloat = std::ceil(lowMinFloat); - highMinFloat = std::ceil(highMinFloat); + lowMinFloat = modm::ceil(lowMinFloat); + highMinFloat = modm::ceil(highMinFloat); if(lowMinFloat > 255 || highMinFloat > 255) { return {false, 255, 255}; @@ -307,7 +309,7 @@ class I2cTimingCalculator auto targetSclHighTime = targetSclTime - sclLowTime - params.riseTime - params.fallTime; - auto targetSclHigh = std::round((targetSclHighTime - SyncTime) / clockPeriod - 1); + auto targetSclHigh = modm::round((targetSclHighTime - SyncTime) / clockPeriod - 1); return (uint8_t) std::max(min, std::min(targetSclHigh, max)); } diff --git a/src/modm/platform/i2c/stm32-extended/module.lb b/src/modm/platform/i2c/stm32-extended/module.lb index 5c2a6124a8..bbc19af566 100644 --- a/src/modm/platform/i2c/stm32-extended/module.lb +++ b/src/modm/platform/i2c/stm32-extended/module.lb @@ -88,6 +88,7 @@ def prepare(module, options): ":architecture:interrupt", ":cmsis:device", ":container", + ":math:utils", ":platform:gpio") global_properties["shared_irqs"] = defaultdict(list) diff --git a/src/modm/platform/i2c/stm32/i2c_master.hpp.in b/src/modm/platform/i2c/stm32/i2c_master.hpp.in index 3422889b63..7252ac1106 100644 --- a/src/modm/platform/i2c/stm32/i2c_master.hpp.in +++ b/src/modm/platform/i2c/stm32/i2c_master.hpp.in @@ -18,6 +18,7 @@ #include #include #include +#include namespace modm { @@ -93,7 +94,7 @@ public: // => y = x * m + b, with m = -2.3333ns/kHz, b = 1'233.3333ns constexpr float max_rise_time = -2.333333f * (float(baudrate) / 1'000.f) + 1'233.333333f; // calculate trise - constexpr float trise_raw = max_rise_time < 0 ? 0 : std::floor(max_rise_time / (1'000.f / freq)); + constexpr float trise_raw = max_rise_time < 0 ? 0 : modm::floor(max_rise_time / (1'000.f / freq)); constexpr uint8_t trise = trise_raw > 62 ? 63 : (trise_raw + 1); initializeWithPrescaler(freq, trise, prescaler, isrPriority); diff --git a/src/modm/platform/i2c/stm32/module.lb b/src/modm/platform/i2c/stm32/module.lb index eee1cfadda..d180a89e4d 100644 --- a/src/modm/platform/i2c/stm32/module.lb +++ b/src/modm/platform/i2c/stm32/module.lb @@ -61,6 +61,7 @@ def prepare(module, options): ":architecture:i2c", ":architecture:interrupt", ":math:algorithm", + ":math:utils", ":cmsis:device", ":container", ":platform:gpio") diff --git a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in index acedaa2ba9..affb3108cf 100644 --- a/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in +++ b/src/modm/platform/spi/at90_tiny_mega_uart/uart_spi_master.hpp.in @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -92,8 +93,8 @@ public: // calculate the fractional prescaler value constexpr float pre_raw = static_cast(SystemClock::UsartSpi) / ( 2 * baudrate ); // respect the prescaler range of 1 to 4096 - constexpr uint32_t pre_ceil = std::ceil(pre_raw) > 4096 ? 4096 : std::ceil(pre_raw); - constexpr uint32_t pre_floor = std::floor(pre_raw) < 1 ? 1 : std::floor(pre_raw); + constexpr uint32_t pre_ceil = modm::ceil(pre_raw) > 4096 ? 4096 : modm::ceil(pre_raw); + constexpr uint32_t pre_floor = modm::floor(pre_raw) < 1 ? 1 : modm::floor(pre_raw); // calculate the possible baudrates above and below the requested baudrate constexpr uint32_t baud_lower = SystemClock::UsartSpi / ( 2 * pre_ceil ); diff --git a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in index 958dba720b..fa8ba25893 100644 --- a/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in +++ b/src/modm/platform/spi/stm32h7/spi_hal_impl.hpp.in @@ -188,8 +188,9 @@ SpiHal{{ id }}::write16(uint16_t data) { // Write with 16-bit access // SPI{{ id }}->TXDR is of type "volatile uint32_t". - // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. - auto* const [[gnu::may_alias]] ptr = reinterpret_cast<__IO uint16_t*>(&SPI{{ id }}->TXDR); + // may_alias is required to avoid undefined behaviour due to strict aliasing violations. + using Reg16 = __IO uint16_t __attribute__((__may_alias__)); + auto* const ptr = reinterpret_cast(&SPI{{ id }}->TXDR); *ptr = data; } @@ -211,8 +212,9 @@ SpiHal{{ id }}::read16() { // Read with 16-bit access // SPI{{ id }}->RXDR is of type "const volatile uint32_t". - // [[gnu::may_alias]] is required to avoid undefined behaviour due to strict aliasing violations. - auto* const [[gnu::may_alias]] ptr = reinterpret_cast(&SPI{{ id }}->RXDR); + // may_alias is required to avoid undefined behaviour due to strict aliasing violations. + using Reg16 = const __IO uint16_t __attribute__((__may_alias__)); + auto* const ptr = reinterpret_cast(&SPI{{ id }}->RXDR); return *ptr; } diff --git a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in index b8ddcaf9c4..f72bed6b45 100644 --- a/src/modm/platform/timer/stm32/general_purpose_base.hpp.in +++ b/src/modm/platform/timer/stm32/general_purpose_base.hpp.in @@ -331,7 +331,7 @@ protected: static consteval int signalToChannel() { - modm::platform::detail::SignalConnection{}; + (void) modm::platform::detail::SignalConnection{}; %% for signal, number in signals %% if loop.first if constexpr (Signal::Signal == Gpio::Signal::{{ signal }}) { diff --git a/src/modm/ui/color/brightness.hpp b/src/modm/ui/color/brightness.hpp index e82fa98e24..82e1d22f7b 100644 --- a/src/modm/ui/color/brightness.hpp +++ b/src/modm/ui/color/brightness.hpp @@ -73,8 +73,7 @@ class BrightnessT * * @param rgb RGB Color */ - template - constexpr BrightnessT(RgbT rgb) + constexpr BrightnessT(RgbT rgb) : value((0.2125f * float(rgb.red)) + (0.7154f * float(rgb.green)) + (0.0721f * float(rgb.blue))) {} @@ -84,8 +83,7 @@ class BrightnessT * * @param hsv HSV Color */ - template - constexpr BrightnessT(HsvT hsv) : value(hsv.value) + constexpr BrightnessT(HsvT hsv) : value(hsv.value) {} /** @@ -97,6 +95,21 @@ class BrightnessT constexpr bool operator==(const BrightnessT &other) const = default; + + constexpr operator RgbT() const + { + return RgbT{value, value, value}; + } + + constexpr operator HsvT() const + { + return HsvT{0, 0, value}; + } + + constexpr operator Rgb565() const + { + return Rgb565(RgbT(*this)); + } }; /// @ingroup modm_ui_color diff --git a/src/modm/ui/color/hsv.hpp b/src/modm/ui/color/hsv.hpp index 76de4a6b28..06891228f6 100644 --- a/src/modm/ui/color/hsv.hpp +++ b/src/modm/ui/color/hsv.hpp @@ -19,22 +19,11 @@ #include #include -#include "brightness.hpp" #include "rgb.hpp" -#include "rgb565.hpp" namespace modm::color { -// forward declarations for convertion constructors -template -class RgbT; - -template -class BrightnessT; - -class Rgb565; - /** * @brief Color in HSV Colorspace * @@ -76,28 +65,14 @@ class HsvT * * @param rgb RGB Color */ - template - constexpr HsvT(const RgbT& rgb); - - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - template - constexpr HsvT(const BrightnessT gray) : hue(0), saturation(0), value(gray.value) - {} - - /** - * Convertion Constructor for RGB565 Color - * - * @param rgb565 RGB565 Color - */ - constexpr HsvT(const Rgb565& rgb565) : HsvT(RgbT(rgb565)) {} + constexpr HsvT(const RgbT& rgb); constexpr bool operator==(const HsvT& other) const = default; + constexpr operator RgbT() const + requires std::is_same_v; + private: template friend IOStream& diff --git a/src/modm/ui/color/hsv_impl.hpp b/src/modm/ui/color/hsv_impl.hpp index c9851b787f..82b2772484 100644 --- a/src/modm/ui/color/hsv_impl.hpp +++ b/src/modm/ui/color/hsv_impl.hpp @@ -24,8 +24,7 @@ * @param rgb */ template -template -constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) +constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) { using CalcType = float; const CalcType maxValue = std::numeric_limits::max(); @@ -69,3 +68,33 @@ constexpr modm::color::HsvT::HsvT(const modm::color::RgbT &rgb) else saturation = _diff / _max * maxValue; } + +template +constexpr modm::color::HsvT::operator RgbT() const + requires std::is_same_v +{ + uint16_t vs = value * saturation; + uint16_t h6 = 6 * hue; + + T p = ((value << 8) - vs) >> 8; + T i = h6 >> 8; + uint16_t f = ((i | 1) << 8) - h6; + if (i & 1) { f = -f; } + T u = (((uint32_t)value << 16) - (uint32_t)vs * f) >> 16; + + uint8_t red = 0; + uint8_t green = 0; + uint8_t blue = 0; + + switch (i) + { + case 0: red = value; green = u; blue = p; break; + case 1: red = u; green = value; blue = p; break; + case 2: red = p; green = value; blue = u; break; + case 3: red = p; green = u; blue = value; break; + case 4: red = u; green = p; blue = value; break; + case 5: red = value; green = p; blue = u; break; + } + + return RgbT(red, green, blue); +} diff --git a/src/modm/ui/color/rgb.hpp b/src/modm/ui/color/rgb.hpp index 788043766a..0b0d1c4792 100644 --- a/src/modm/ui/color/rgb.hpp +++ b/src/modm/ui/color/rgb.hpp @@ -24,22 +24,9 @@ #include #include -#include "brightness.hpp" -#include "hsv.hpp" -#include "rgb565.hpp" - namespace modm::color { -// forward declarations for convertion constructors -template -class HsvT; - -template -class BrightnessT; - -class Rgb565; - /** * Color in HSV Colorspace * @@ -78,36 +65,6 @@ class RgbT : red(rgb_other.red >> 8), green(rgb_other.green >> 8), blue(rgb_other.blue >> 8) {} - /** - * Convertion Constructor for HSV Color - * - * @param hsv HSV Color - */ - template - constexpr RgbT(const HsvT& hsv); - - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - // TODO Plump conversion, implement the right way - template - constexpr RgbT(const BrightnessT brightness) - : red(brightness), green(brightness), blue(brightness) - {} - - /** - * Convertion Constructor for RGB565 Color - * - * @param rgb565 RGB565 Color - */ - constexpr RgbT(const Rgb565& rgb565) - : red((rgb565.color >> 8) & 0xF8), - green((rgb565.color >> 3) & 0xFC), - blue(rgb565.color << 3) - {} - constexpr bool operator==(const RgbT& other) const = default; @@ -160,6 +117,4 @@ operator<<(IOStream& os, const color::RgbT& color) } // namespace modm::color -#include "rgb_impl.hpp" - #endif // MODM_COLOR_RGB_HPP diff --git a/src/modm/ui/color/rgb565.hpp b/src/modm/ui/color/rgb565.hpp index dbb19aba56..262729fbf9 100644 --- a/src/modm/ui/color/rgb565.hpp +++ b/src/modm/ui/color/rgb565.hpp @@ -13,23 +13,12 @@ #include -#include "brightness.hpp" #include "hsv.hpp" #include "rgb.hpp" namespace modm::color { -// forward declarations for convertion constructors -template -class RgbT; - -template -class HsvT; - -template -class BrightnessT; - /** * Color in RGB Colorspace, 16 bits: RRRR RGGG GGGB BBBB * @@ -81,18 +70,22 @@ class Rgb565 constexpr Rgb565(const HsvT &hsv) : Rgb565(RgbCalcType(hsv)) {} - /** - * Convertion Constructor for Brightness - * - * @param brightness Brightness 'Color'-object - */ - template - constexpr Rgb565(const BrightnessT brightness) : Rgb565(RgbCalcType(brightness)) - {} - constexpr bool operator==(const Rgb565 &other) const = default; + constexpr operator RgbCalcType() const + { + const uint8_t red = (color >> 8) & 0xF8; + const uint8_t green = (color >> 3) & 0xFC; + const uint8_t blue = (color << 3); + return RgbCalcType(red, green, blue); + } + + constexpr operator Hsv() const + { + return Hsv(RgbT(*this)); + } + /// Saturated addition ⊕ Rgb565 operator+(const Rgb565 other) const diff --git a/src/modm/ui/color/rgb_impl.hpp b/src/modm/ui/color/rgb_impl.hpp deleted file mode 100644 index b8b9a17841..0000000000 --- a/src/modm/ui/color/rgb_impl.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2009-2010, 2012, Fabian Greif - * Copyright (c) 2010, Martin Rosekeit - * Copyright (c) 2012-2013, Niklas Hauser - * Copyright (c) 2013, David Hebbeker - * Copyright (c) 2021, Thomas Sommer - * - * This file is part of the modm project. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -// ---------------------------------------------------------------------------- - -#ifndef MODM_COLOR_RGB_HPP -#error "Don't include this file directly, use 'rgb.hpp' instead!" -#endif - -#include - -namespace modm::color -{ - -// TODO Finish generalisation for uint16_t -template -template -constexpr RgbT::RgbT(const HsvT &hsv) -{ - uint16_t vs = hsv.value * hsv.saturation; - uint16_t h6 = 6 * hsv.hue; - - T p = ((hsv.value << 8) - vs) >> 8; - T i = h6 >> 8; - uint16_t f = ((i | 1) << 8) - h6; - if (i & 1) { f = -f; } - T u = (((uint32_t)hsv.value << 16) - (uint32_t)vs * f) >> 16; - - switch (i) - { - case 0: red = hsv.value; green = u; blue = p; break; - case 1: red = u; green = hsv.value; blue = p; break; - case 2: red = p; green = hsv.value; blue = u; break; - case 3: red = p; green = u; blue = hsv.value; break; - case 4: red = u; green = p; blue = hsv.value; break; - case 5: red = hsv.value; green = p; blue = u; break; - } -} - -} // namespace modm::color diff --git a/src/modm/utils/aligned_storage.hpp b/src/modm/utils/aligned_storage.hpp index 4c7fdc3312..fc7c6f741c 100644 --- a/src/modm/utils/aligned_storage.hpp +++ b/src/modm/utils/aligned_storage.hpp @@ -47,9 +47,13 @@ union aligned_storage_helper maybe f; maybe g; maybe h; + maybe i; }; } // namespace aligned_storage_impl +template +constexpr auto default_storage_alignment = alignof(aligned_storage_impl::aligned_storage_helper); + /** * Implementation of std::aligned_storage that avoids GCC bug #61458 which can * cause excessive size for types smaller than the maximum alignment. @@ -58,15 +62,20 @@ union aligned_storage_helper * The implementation is derived from: * https://github.com/WG21-SG14/SG14/blob/master/SG14/inplace_function.h */ -template)> -struct aligned_storage { - using type = std::aligned_storage_t; +template> +struct [[deprecated("see C++ standards paper P1413R3")]] aligned_storage // DEPRECATED: 2026q3 +{ + struct type + { + alignas(Align) unsigned char data[Cap]; + }; }; /// @endcond /// @ingroup modm_utils -template)> -using aligned_storage_t = typename aligned_storage::type; +template> +using aligned_storage_t [[deprecated("see C++ standards paper P1413R3")]] = + typename aligned_storage::type; // DEPRECATED: 2026q3 static_assert(sizeof(aligned_storage_t) == sizeof(void*)); static_assert(alignof(aligned_storage_t) == alignof(void*)); diff --git a/src/modm/utils/inplace_any.hpp b/src/modm/utils/inplace_any.hpp index 80dfa4fb22..165c1c8dd7 100644 --- a/src/modm/utils/inplace_any.hpp +++ b/src/modm/utils/inplace_any.hpp @@ -207,7 +207,7 @@ class inplace_any final void move_from(inplace_any&& other) noexcept; private: - modm::aligned_storage::type storage_; + alignas(default_storage_alignment) std::byte storage_[Size]; inplace_any_impl::HandlerFunc handler_{nullptr}; }; diff --git a/src/modm/utils/inplace_function.hpp b/src/modm/utils/inplace_function.hpp index 793329e303..655da9b398 100644 --- a/src/modm/utils/inplace_function.hpp +++ b/src/modm/utils/inplace_function.hpp @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -118,7 +119,7 @@ struct is_valid_inplace_dst : std::true_type template< class Signature, size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity, - size_t Alignment = alignof(modm::aligned_storage_t) + size_t Alignment = default_storage_alignment > class inplace_function; // unspecified @@ -140,7 +141,6 @@ template< > class inplace_function { - using storage_t = modm::aligned_storage_t; using vtable_t = inplace_function_detail::vtable; using vtable_ptr_t = const vtable_t*; @@ -275,7 +275,7 @@ class inplace_function { if (this == std::addressof(other)) return; - storage_t tmp; + alignas(Alignment) std::byte tmp[Capacity]; vtable_ptr_->relocate_ptr( std::addressof(tmp), std::addressof(storage_) @@ -301,7 +301,7 @@ class inplace_function private: vtable_ptr_t vtable_ptr_; - mutable storage_t storage_; + alignas(Alignment) mutable std::byte storage_[Capacity]; inplace_function( vtable_ptr_t vtable_ptr, diff --git a/test/modm/math/utils/consteval_cmath_test.cpp b/test/modm/math/utils/consteval_cmath_test.cpp new file mode 100644 index 0000000000..86226d6dc0 --- /dev/null +++ b/test/modm/math/utils/consteval_cmath_test.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2026, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "consteval_cmath_test.hpp" +#include + +namespace { + +template +constexpr bool +is_negative_zero(T value) +{ + if (value != T{0}) return false; + return std::signbit(value); +} + +static_assert(!is_negative_zero(0.1f)); +static_assert(!is_negative_zero(0.0f)); +static_assert(is_negative_zero(-0.0f)); +static_assert(!is_negative_zero(0.1)); +static_assert(!is_negative_zero(0.0)); +static_assert(is_negative_zero(-0.0)); + +// 2^60 + 2^40 +template +constexpr T LargeNumber = T{1152922604118474752.0l}; + +} // namespace + +void +ConstevalCmathTest::testCeilFloat() +{ + using modm::detail::consteval_ceil; + + // Standard rounding + static_assert(consteval_ceil(1.1f) == 2.0f); + static_assert(consteval_ceil(1.9f) == 2.0f); + static_assert(consteval_ceil(-1.1f) == -1.0f); + static_assert(consteval_ceil(-1.9f) == -1.0f); + + // Integers should remain unchanged + static_assert(consteval_ceil(2.0f) == 2.0f); + static_assert(consteval_ceil(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_ceil(0.0f) == 0.0f); + static_assert(!is_negative_zero(consteval_ceil(0.0f))); + static_assert(is_negative_zero(consteval_ceil(-0.0f))); + static_assert(is_negative_zero(consteval_ceil(-0.5f))); + + // Mantissa Limit (2^23) + static_assert(consteval_ceil(8388607.5f) == 8388608.0f); // 2^23 - 0.5 + static_assert(consteval_ceil(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_ceil(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_ceil(LargeNumber) == LargeNumber); + static_assert(consteval_ceil(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_ceil(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_ceil(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_ceil(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_ceil(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testCeilDouble() +{ + using modm::detail::consteval_ceil; + + // Standard rounding + static_assert(consteval_ceil(1.1) == 2.0); + static_assert(consteval_ceil(1.9) == 2.0); + static_assert(consteval_ceil(-1.1) == -1.0); + static_assert(consteval_ceil(-1.9) == -1.0); + + // Integers should remain unchanged + static_assert(consteval_ceil(2.0) == 2.0); + static_assert(consteval_ceil(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_ceil(0.0) == 0.0); + static_assert(!is_negative_zero(consteval_ceil(0.0))); + static_assert(is_negative_zero(consteval_ceil(-0.0))); + static_assert(is_negative_zero(consteval_ceil(-0.5))); + + // Mantissa Limit (2^52) + static_assert(consteval_ceil(4503599627370495.5) == 4503599627370496.0); // 2^52 - 0.5 + static_assert(consteval_ceil(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_ceil(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers (2^60 + 2^40) + static_assert(consteval_ceil(LargeNumber) == LargeNumber); + static_assert(consteval_ceil(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_ceil(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_ceil(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_ceil(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_ceil(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testFloorFloat() +{ + using modm::detail::consteval_floor; + + // Standard rounding + static_assert(consteval_floor(1.1f) == 1.0f); + static_assert(consteval_floor(1.9f) == 1.0f); + static_assert(consteval_floor(-1.1f) == -2.0f); + static_assert(consteval_floor(-1.9f) == -2.0f); + + // Integers should remain unchanged + static_assert(consteval_floor(2.0f) == 2.0f); + static_assert(consteval_floor(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_floor(0.0f) == 0.0f); + static_assert(consteval_floor(-0.0f) == -0.0f); + static_assert(!is_negative_zero(consteval_floor(0.0f))); + static_assert(is_negative_zero(consteval_floor(-0.0f))); + + // Mantissa Limit (2^23) + static_assert(consteval_floor(8388607.5f) == 8388607.0f); // 2^23 - 0.5 + static_assert(consteval_floor(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_floor(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_floor(LargeNumber) == LargeNumber); + static_assert(consteval_floor(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_floor(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_floor(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_floor(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_floor(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testFloorDouble() +{ + using modm::detail::consteval_floor; + + // Standard rounding + static_assert(consteval_floor(1.1) == 1.0); + static_assert(consteval_floor(1.9) == 1.0); + static_assert(consteval_floor(-1.1) == -2.0); + static_assert(consteval_floor(-1.9) == -2.0); + + // Integers should remain unchanged + static_assert(consteval_floor(2.0) == 2.0); + static_assert(consteval_floor(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_floor(0.0) == 0.0); + static_assert(consteval_floor(-0.0) == -0.0); + static_assert(!is_negative_zero(consteval_floor(0.0))); + static_assert(is_negative_zero(consteval_floor(-0.0))); + + // Mantissa Limit (2^52) + static_assert(consteval_floor(4503599627370495.5) == 4503599627370495.0); // 2^52 - 0.5 + static_assert(consteval_floor(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_floor(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers + static_assert(consteval_floor(LargeNumber) == LargeNumber); + static_assert(consteval_floor(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_floor(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_floor(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_floor(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_floor(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testRoundFloat() +{ + using modm::detail::consteval_round; + + // Standard rounding + static_assert(consteval_round(1.1f) == 1.0f); + static_assert(consteval_round(1.4f) == 1.0f); + static_assert(consteval_round(1.5f) == 2.0f); + static_assert(consteval_round(1.9f) == 2.0f); + static_assert(consteval_round(-1.1f) == -1.0f); + static_assert(consteval_round(-1.4f) == -1.0f); + static_assert(consteval_round(-1.5f) == -2.0f); + static_assert(consteval_round(-1.9f) == -2.0f); + + // Integers should remain unchanged + static_assert(consteval_round(2.0f) == 2.0f); + static_assert(consteval_round(-2.0f) == -2.0f); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_round(0.0f) == 0.0f); + static_assert(consteval_round(-0.0f) == -0.0f); + static_assert(consteval_round(-0.4f) == -0.0f); + static_assert(!is_negative_zero(consteval_round(0.0f))); + static_assert(is_negative_zero(consteval_round(-0.0f))); + static_assert(is_negative_zero(consteval_round(-0.4f))); + + // Mantissa Limit (2^23) + static_assert(consteval_round(8388607.5f) == 8388608.0f); // 2^23 - 0.5 + static_assert(consteval_round(8388608.0f) == 8388608.0f); // 2^23 + static_assert(consteval_round(8388609.0f) == 8388609.0f); // 2^23 + 1.0 + + // Large numbers + static_assert(consteval_round(LargeNumber) == LargeNumber); + static_assert(consteval_round(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_round(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_round(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_round(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_round(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} + +void +ConstevalCmathTest::testRoundDouble() +{ + using modm::detail::consteval_round; + + // Standard rounding + static_assert(consteval_round(1.1) == 1.0); + static_assert(consteval_round(1.4) == 1.0); + static_assert(consteval_round(1.5) == 2.0); + static_assert(consteval_round(1.9) == 2.0); + static_assert(consteval_round(-1.1) == -1.0); + static_assert(consteval_round(-1.4) == -1.0); + static_assert(consteval_round(-1.5) == -2.0); + static_assert(consteval_round(-1.9) == -2.0); + + // Integers should remain unchanged + static_assert(consteval_round(2.0) == 2.0); + static_assert(consteval_round(-2.0) == -2.0); + + // Sign of zero (0.0 == -0.0 is true, sign cannot be checked with ==) + static_assert(consteval_round(0.0) == 0.0); + static_assert(consteval_round(-0.0) == -0.0); + static_assert(!is_negative_zero(consteval_round(0.0))); + static_assert(is_negative_zero(consteval_round(-0.0))); + + // Mantissa Limit (2^52) + static_assert(consteval_round(4503599627370495.5) == 4503599627370496.0); // 2^52 - 0.5 + static_assert(consteval_round(4503599627370496.0) == 4503599627370496.0); // 2^52 + static_assert(consteval_round(4503599627370497.0) == 4503599627370497.0); // 2^52 + 1.0 + + // Large numbers + static_assert(consteval_round(LargeNumber) == LargeNumber); + static_assert(consteval_round(-LargeNumber) == -LargeNumber); + + // Infinity + static_assert(consteval_round(std::numeric_limits::infinity()) == + std::numeric_limits::infinity()); + + static_assert(consteval_round(-std::numeric_limits::infinity()) == + -std::numeric_limits::infinity()); + + // NaN + static_assert(std::isnan(consteval_round(std::numeric_limits::quiet_NaN()))); + static_assert(std::isnan(consteval_round(std::numeric_limits::signaling_NaN()))); + + // dummy, test is compile time only + TEST_ASSERT_TRUE(true); +} diff --git a/test/modm/math/utils/consteval_cmath_test.hpp b/test/modm/math/utils/consteval_cmath_test.hpp new file mode 100644 index 0000000000..fca705cb10 --- /dev/null +++ b/test/modm/math/utils/consteval_cmath_test.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_math +class ConstevalCmathTest : public unittest::TestSuite +{ +public: + void + testCeilFloat(); + + void + testCeilDouble(); + + void + testFloorFloat(); + + void + testFloorDouble(); + + void + testRoundFloat(); + + void + testRoundDouble(); +}; diff --git a/test/modm/utils/inplace_function_test.cpp b/test/modm/utils/inplace_function_test.cpp new file mode 100644 index 0000000000..8b8a684bfb --- /dev/null +++ b/test/modm/utils/inplace_function_test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "inplace_function_test.hpp" +#include + +void +InplaceFunctionTest::testCall() +{ + int value = 0; + + modm::inplace_function func = [&value](int x) { + value = x; + return x * 2; + }; + + TEST_ASSERT_EQUALS(func(42), 84); + TEST_ASSERT_EQUALS(value, 42); +} diff --git a/test/modm/utils/inplace_function_test.hpp b/test/modm/utils/inplace_function_test.hpp new file mode 100644 index 0000000000..75610b8b7d --- /dev/null +++ b/test/modm/utils/inplace_function_test.hpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026, Christopher Durand + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_utils +class InplaceFunctionTest : public unittest::TestSuite +{ +public: + void + testCall(); +};