I am writing a class to setup a serial port on an AVR microcontroller. I have a template function that takes as parameters a cpu clock value and a desired baud rate, does a quick calculation, verifies if the actual value is within 1.5% margin from the desired value with static asserts, then returns the actual value to set inside an 8 bit register. I need to use std::round and its return value needs to be constexpr to have everything evaluated at compile time. This is the problematic bit:
#include <cmath>
template<int c, int b>
constexpr int UBRRValue() {
// return the value for UBRR register to get close to the desired baud rate
// avoid integer division
return std::round( static_cast<float>(c) / ( 16 * b ) - 1 );
}
int main() {
constexpr auto val = UBRRValue<2000,25>();
return val;
}
This works fine for x86 on compiler explorer, it returns 4. On AVR there is no cmath, float round(float) is defined in math.h and implemented in assembly, so probably not constexpr. After a quick search I found this: https://stackoverflow.com/a/24348037/11221049 I made a few adjustments, to then have gcc point out that this function is non-constexpr. I made it constexpr, but then its result will never be constexpr because it requires access to a union member that has not been initialized. Union tricks are not constexpr. So... is it possible to make a constexpr round function (knowing that anything from math.h is written directly in assembly)? How is it done in gnu libc++?
What you are trying to compute is the correctly rounded result of (c / (16 * b)) - 1
. You are casting to float to avoid integer division, but this is almost pointless if you're going to round afterwards anyway.
Notice that we can move the -1
outside the rounding safely (will only change the result if you were discarding the -1
due to lack of float precision, which you don't seem to intend). So all we need is the correctly rounded result of c / (16*b)
. If we do this as integer division we get the rounded-down result. We can get a midway-rounded result by just adding half the divisor to the dividend (assuming that both are positive):
template<int c, int b>
constexpr int UBRRValue() {
// return the value for UBRR register to get close to the desired baud rate
return (c + 8*b) / (16 * b) - 1;
}
Here are some test cases it passes: https://godbolt.org/z/Va6qDT