可以为AVR编写constexpr舍入函数吗?

时间:2019-03-27 16:49:52

标签: c++ rounding avr constexpr

我正在编写一个类来设置AVR微控制器上的串行端口。我有一个模板函数,将cpu时钟值和所需的波特率作为参数,进行快速计算,使用静态断言验证实际值是否在所需值的1.5%范围内,然后返回实际值以在内部设置一个8位寄存器。我需要使用std :: round,并且其返回值需要为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在编译器资源管理器上正常工作,返回4。 在AVR上没有cmath,在math.h中定义并在汇编中实现float round(float),因此可能不是constexpr。经过快速搜索,我发现了这一点:https://stackoverflow.com/a/24348037/11221049 我进行了一些调整,然后让gcc指出此函数是非constexpr。我将其设为constexpr,但是其结果将永远不会是constexpr,因为它需要访问尚未初始化的联合成员。联合技巧不是constexpr。 那么...有可能制作一个constexpr round函数(知道math.h中的任何内容都是直接用汇编语言编写的)吗? 在gnu libc ++中如何完成?

2 个答案:

答案 0 :(得分:2)

您要计算的是(c / (16 * b)) - 1的正确取整结果。您为了避免整数除法而将其强制转换为浮点数,但是如果以后仍要舍入,这几乎是没有意义的。

请注意,我们可以安全地将-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

答案 1 :(得分:0)

舍入浮点值始终可以通过简单地相加或减去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;
}

如果您确定函数始终是常量评估的,则可以安全地使用double而不是float,因为它们无论如何都不会存储在闪存中。

/编辑 正如评论中提到的那样,这种“始终”有效的说法是不正确的。对于这种情况就足够了,因为波特率寄存器既不能为负也不为0。