是否可以为 AVR 编写一个 constexpr 舍入函数?
Is it possible to write a constexpr rounding function for AVR?
我正在写 class 来设置 AVR 微控制器上的串口。我有一个模板函数,它将 cpu 时钟值和所需的波特率作为参数,进行快速计算,使用静态断言验证实际值是否在所需值的 1.5% 范围内,然后 returns 在 8 位寄存器中设置的实际值。我需要使用 std::round 并且它的 return 值需要是 constexpr 才能在编译时评估所有内容。这是有问题的一点:
#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;
}
这在编译器资源管理器上适用于 x86,它 returns 4。
在 AVR 上没有 cmath,float round(float) 在 math.h 中定义并在汇编中实现,因此可能不是 constexpr。快速搜索后,我发现了这个:
我做了一些调整,然后让 gcc 指出这个函数是非 constexpr。我将它设为 constexpr,但它的结果永远不会是 constexpr,因为它需要访问一个尚未初始化的联合成员。 Union 技巧不是 constexpr。
那么...是否有可能制作一个 constexpr round 函数(知道 math.h 中的任何内容都是直接用汇编编写的)?
在 gnu libc++ 中是如何完成的?
舍入浮点值总是可以通过简单地加上或减去 0.5 然后转换回整数来完成。无需从 std:: 命名空间调用任何内容。
constexpr int round(double x) {
return (x >= 0.0) ? int(x + 0.5) : int(x - 0.5);
}
constexpr int UBRRValue(int c, int b) {
return round(static_cast<double>(c) / (16 * b) - 1);
}
int main() {
constexpr auto val = UBRRValue(2000, 25);
return val;
}
如果您确定函数始终是常量求值的,您可以安全地使用双精度而不是浮点数,因为它们无论如何都不会在闪存中结束。
/编辑
如评论中所述,此 "always" 有效的说法是不正确的。对于这种情况已经足够了,因为波特率寄存器既不是负值也不是 0。
您要计算的是 (c / (16 * b)) - 1
的正确舍入结果。您正在转换为 float 以避免整数除法,但是如果您之后要四舍五入,这几乎毫无意义。
请注意,我们可以将 -1
安全地移动到舍入之外(如果您由于缺乏浮点精度而丢弃 -1
只会更改结果,而您似乎没有打算)。所以我们所需要的只是 c / (16*b)
的正确舍入结果。如果我们这样做作为整数除法,我们会得到向下舍入的结果。我们只需将除数的一半加到被除数上(假设两者都是正数),就可以得到一个中间舍入的结果:
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;
}
以下是它通过的一些测试用例:https://godbolt.org/z/Va6qDT
我正在写 class 来设置 AVR 微控制器上的串口。我有一个模板函数,它将 cpu 时钟值和所需的波特率作为参数,进行快速计算,使用静态断言验证实际值是否在所需值的 1.5% 范围内,然后 returns 在 8 位寄存器中设置的实际值。我需要使用 std::round 并且它的 return 值需要是 constexpr 才能在编译时评估所有内容。这是有问题的一点:
#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;
}
这在编译器资源管理器上适用于 x86,它 returns 4。 在 AVR 上没有 cmath,float round(float) 在 math.h 中定义并在汇编中实现,因此可能不是 constexpr。快速搜索后,我发现了这个: 我做了一些调整,然后让 gcc 指出这个函数是非 constexpr。我将它设为 constexpr,但它的结果永远不会是 constexpr,因为它需要访问一个尚未初始化的联合成员。 Union 技巧不是 constexpr。 那么...是否有可能制作一个 constexpr round 函数(知道 math.h 中的任何内容都是直接用汇编编写的)? 在 gnu libc++ 中是如何完成的?
舍入浮点值总是可以通过简单地加上或减去 0.5 然后转换回整数来完成。无需从 std:: 命名空间调用任何内容。
constexpr int round(double x) {
return (x >= 0.0) ? int(x + 0.5) : int(x - 0.5);
}
constexpr int UBRRValue(int c, int b) {
return round(static_cast<double>(c) / (16 * b) - 1);
}
int main() {
constexpr auto val = UBRRValue(2000, 25);
return val;
}
如果您确定函数始终是常量求值的,您可以安全地使用双精度而不是浮点数,因为它们无论如何都不会在闪存中结束。
/编辑 如评论中所述,此 "always" 有效的说法是不正确的。对于这种情况已经足够了,因为波特率寄存器既不是负值也不是 0。
您要计算的是 (c / (16 * b)) - 1
的正确舍入结果。您正在转换为 float 以避免整数除法,但是如果您之后要四舍五入,这几乎毫无意义。
请注意,我们可以将 -1
安全地移动到舍入之外(如果您由于缺乏浮点精度而丢弃 -1
只会更改结果,而您似乎没有打算)。所以我们所需要的只是 c / (16*b)
的正确舍入结果。如果我们这样做作为整数除法,我们会得到向下舍入的结果。我们只需将除数的一半加到被除数上(假设两者都是正数),就可以得到一个中间舍入的结果:
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;
}
以下是它通过的一些测试用例:https://godbolt.org/z/Va6qDT