Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
It doesn't really need to be carried out in the preprocessor as such, it is sufficient to realize that a constant expression in C, consisting only of constants ("literals") is always calculated at ...
#2: Post edited
- It doesn't really need to be carried out in the preprocessor as such, it is sufficient to realize that a constant expression in C, consisting only of constants ("literals") is always calculated at compile-time.
- The actual math for doing rounding is then rather trivial. Example:
- float some_float = 3.14f;
- uint32_t result = some_float - (uint32_t)some_float > 0.5f ?
- (uint32_t) some_float+1 :
- (uint32_t) some_float;
- The compiler with optimizations enabled will just replace that with `3` at compile-time.
- Turning that same code into a macro:
- ```c
#define ROUND(flt) ((flt)-(uint32_t)flt > 0.5f ? \(uint32_t)flt+1 : \(uint32_t)flt ) \- ```
- As long as `flt` is a constant expression, this will all get calculated at compile-time.
- ---
- **Regarding type safety:**
- The above macro would be type-generic, as in the macro will accept any arithmetic expression containing, `float`, `double`, `int` etc and return a `uint32_t`. The advantage/disadvantage is that a lot of constant expressions might not always have an obvious type due to implicit type promotion. For example something like `true ? 1 : 0.0` always returns `1.0` and in a type that is `double`.
- If we want to allow that or not depends on how stringent we are with types. In some hardware-restricted applications like microcontroller ones, there may not be a FPU present at all (very common) or there might be a FPU but only single-precision (Cortex M3 etc). In some cases, a target-aware IDE might decide to link in a big software floating-point lib just because it spotted the presence of a floating point type not supported by hardware, and that's generally a bad thing. _Especially_ if we only wanted to do floating point as part of the preprocessor and keep all run-time calculations in fixed point, because we are using some low- to mid-range MCU.
- Casting the expression back to an integer type may or may not mean that the IDE will not link in that software floating point lib. To avoid such situations in certain IDEs, then a type safe macro makes sense, to prevent that from happening by accident.
- By taking some of the tricks from [How to create meaningful error messages from _Generic macros?](https://stackoverflow.com/questions/79735440/how-to-create-meaningful-error-messages-from-generic-macros), such a type safe macro with meaningful compiler errors might look like this:
- ```c
- #define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
- #define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
- #define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
- , /* comma operator */ \
- ((flt)-(uint32_t)flt > 0.5f ? \
- (uint32_t)flt+1 : \
- (uint32_t)flt ) \
- )
- ```
- Complete example:
- ```c
- #include <stdint.h>
- #include <stdio.h>
- #include <inttypes.h>
- #define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
- #define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
- #define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
- , /* comma operator */ \
- ((flt)-(uint32_t)flt > 0.5f ? \
- (uint32_t)flt+1 : \
- (uint32_t)flt ) \
- )
- int main()
- {
- printf("%"PRIu32 "\n", ROUNDF(3.14f));
- printf("%"PRIu32 "\n", ROUNDF(3.54f));
- printf("%"PRIu32 "\n", ROUNDF(3.14)); // compiler error
- printf("%"PRIu32 "\n", ROUNDF(3.54)); // compiler error
- printf("%"PRIu32 "\n", ROUNDF(3)); // compiler error
- }
- ```
- It doesn't really need to be carried out in the preprocessor as such, it is sufficient to realize that a constant expression in C, consisting only of constants ("literals") is always calculated at compile-time.
- The actual math for doing rounding is then rather trivial. Example:
- float some_float = 3.14f;
- uint32_t result = some_float - (uint32_t)some_float > 0.5f ?
- (uint32_t) some_float+1 :
- (uint32_t) some_float;
- The compiler with optimizations enabled will just replace that with `3` at compile-time.
- Turning that same code into a macro:
- ```c
- #define ROUND(flt) ( (flt)-(uint32_t)flt > 0.5f ? \
- (uint32_t)flt+1 : \
- (uint32_t)flt )
- ```
- As long as `flt` is a constant expression, this will all get calculated at compile-time.
- ---
- **Regarding type safety:**
- The above macro would be type-generic, as in the macro will accept any arithmetic expression containing, `float`, `double`, `int` etc and return a `uint32_t`. The advantage/disadvantage is that a lot of constant expressions might not always have an obvious type due to implicit type promotion. For example something like `true ? 1 : 0.0` always returns `1.0` and in a type that is `double`.
- If we want to allow that or not depends on how stringent we are with types. In some hardware-restricted applications like microcontroller ones, there may not be a FPU present at all (very common) or there might be a FPU but only single-precision (Cortex M3 etc). In some cases, a target-aware IDE might decide to link in a big software floating-point lib just because it spotted the presence of a floating point type not supported by hardware, and that's generally a bad thing. _Especially_ if we only wanted to do floating point as part of the preprocessor and keep all run-time calculations in fixed point, because we are using some low- to mid-range MCU.
- Casting the expression back to an integer type may or may not mean that the IDE will not link in that software floating point lib. To avoid such situations in certain IDEs, then a type safe macro makes sense, to prevent that from happening by accident.
- By taking some of the tricks from [How to create meaningful error messages from _Generic macros?](https://stackoverflow.com/questions/79735440/how-to-create-meaningful-error-messages-from-generic-macros), such a type safe macro with meaningful compiler errors might look like this:
- ```c
- #define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
- #define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
- #define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
- , /* comma operator */ \
- ((flt)-(uint32_t)flt > 0.5f ? \
- (uint32_t)flt+1 : \
- (uint32_t)flt ) \
- )
- ```
- Complete example:
- ```c
- #include <stdint.h>
- #include <stdio.h>
- #include <inttypes.h>
- #define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
- #define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
- #define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
- , /* comma operator */ \
- ((flt)-(uint32_t)flt > 0.5f ? \
- (uint32_t)flt+1 : \
- (uint32_t)flt ) \
- )
- int main()
- {
- printf("%"PRIu32 "\n", ROUNDF(3.14f));
- printf("%"PRIu32 "\n", ROUNDF(3.54f));
- printf("%"PRIu32 "\n", ROUNDF(3.14)); // compiler error
- printf("%"PRIu32 "\n", ROUNDF(3.54)); // compiler error
- printf("%"PRIu32 "\n", ROUNDF(3)); // compiler error
- }
- ```
#1: Initial revision
It doesn't really need to be carried out in the preprocessor as such, it is sufficient to realize that a constant expression in C, consisting only of constants ("literals") is always calculated at compile-time.
The actual math for doing rounding is then rather trivial. Example:
float some_float = 3.14f;
uint32_t result = some_float - (uint32_t)some_float > 0.5f ?
(uint32_t) some_float+1 :
(uint32_t) some_float;
The compiler with optimizations enabled will just replace that with `3` at compile-time.
Turning that same code into a macro:
```c
#define ROUND(flt) ((flt)-(uint32_t)flt > 0.5f ? \
(uint32_t)flt+1 : \
(uint32_t)flt ) \
```
As long as `flt` is a constant expression, this will all get calculated at compile-time.
---
**Regarding type safety:**
The above macro would be type-generic, as in the macro will accept any arithmetic expression containing, `float`, `double`, `int` etc and return a `uint32_t`. The advantage/disadvantage is that a lot of constant expressions might not always have an obvious type due to implicit type promotion. For example something like `true ? 1 : 0.0` always returns `1.0` and in a type that is `double`.
If we want to allow that or not depends on how stringent we are with types. In some hardware-restricted applications like microcontroller ones, there may not be a FPU present at all (very common) or there might be a FPU but only single-precision (Cortex M3 etc). In some cases, a target-aware IDE might decide to link in a big software floating-point lib just because it spotted the presence of a floating point type not supported by hardware, and that's generally a bad thing. _Especially_ if we only wanted to do floating point as part of the preprocessor and keep all run-time calculations in fixed point, because we are using some low- to mid-range MCU.
Casting the expression back to an integer type may or may not mean that the IDE will not link in that software floating point lib. To avoid such situations in certain IDEs, then a type safe macro makes sense, to prevent that from happening by accident.
By taking some of the tricks from [How to create meaningful error messages from _Generic macros?](https://stackoverflow.com/questions/79735440/how-to-create-meaningful-error-messages-from-generic-macros), such a type safe macro with meaningful compiler errors might look like this:
```c
#define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
#define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
#define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
, /* comma operator */ \
((flt)-(uint32_t)flt > 0.5f ? \
(uint32_t)flt+1 : \
(uint32_t)flt ) \
)
```
Complete example:
```c
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#define STATIC_ASSERT_EXPR(expr, msg) ( (void)(struct{ int dummy; static_assert(expr, msg); }){}.dummy )
#define IS_FLOAT(expr) _Generic((expr), float: true, default: false)
#define ROUNDF(flt) ( STATIC_ASSERT_EXPR(IS_FLOAT(flt), "Wrong argument " #flt " passed to ROUNDF, expected float.") \
, /* comma operator */ \
((flt)-(uint32_t)flt > 0.5f ? \
(uint32_t)flt+1 : \
(uint32_t)flt ) \
)
int main()
{
printf("%"PRIu32 "\n", ROUNDF(3.14f));
printf("%"PRIu32 "\n", ROUNDF(3.54f));
printf("%"PRIu32 "\n", ROUNDF(3.14)); // compiler error
printf("%"PRIu32 "\n", ROUNDF(3.54)); // compiler error
printf("%"PRIu32 "\n", ROUNDF(3)); // compiler error
}
```
