我正在使用bison
为avr
微控制器编写计算器,我有一个2双精度幂的分辨率问题。
bison
文件中,我将类型定义为
%define api.value.type {double}
%token NUMBER
,然后给出以下规则
expr: NUMBER
| expr '^' expr {$$ = pow($1, $3);}
和代码工作正常,除非当我试图计算2^8
,给我255.9999
作为答案,而不是256
。
查看问题是否与double
或pow
,我已经修改了代码如下:
expr: NUMBER
| expr '^' expr {$$ = pow($1, $3);
double a = 2.0; double b = 8.0;
if (a == $1) lcd << "a ok"; // prints ok
if (b == $3) lcd << "b ok"; // prints ok
double c = pow(a, b);
lcd << c; // it shows 256!!!
if ($$ == c) lcd << "$$ ok";
else lcd << "$$ wrong"; // prints wrong!!!!
}
可以看到,函数pow
可以与a
和b
一起工作,这两个变量具有与$1
和$3
相同的值,但$$
与c = pow(a, b)
不同。
我不知道发生了什么事。
这是我第一次使用bison
,所以很可能我做错了什么。
我正在用avr-g++ 9.2.0编译。
谢谢。
编辑:为了看看发生了什么,我用两种不同的方式修改了我的规则:
如果我尝试:
expr: NUMBER
| expr '^' expr {yyval = pow(2.0, 8.0);}
它给我正确的答案并打印256
。
但是如果我尝试:
expr: NUMBER
| expr '^' expr {yyval = pow($1, $3);}
它给了我错误的答案255.9999
这与野牛无关。罪魁祸首是AVR微控制器上的数学库。
当你写(C):
double a = 2.0;
double b = 8.0;
double c = pow(a, b);
Gcc足够聪明,可以计算出c将是256.0。没有必要在运行时进行计算。Gcc只是将其重写为double c = 256.0;
。
Gcc使用它运行的机器上的数学库或它自己的捆绑数学库来进行计算。这很可能是Gnu数学库,它优化了小整数幂的计算。
对pow
的另一个调用是在运行时计算的,因为编译器不知道$1
和$3
将是什么。所以这个调用是用微控制器上的数学库完成的,这是非常不准确的。(它可能会像exp(8.0 * log(2.0))
一样,引入一个小的舍入错误。)
一个可能的解决方案是编写自己的pow
实现,当指数是整数时,它使用更精确的计算。
avr-gcc double默认为32位。对于64位double,您需要avr-gcc v10+,参见GCC发行说明
https://gcc.gnu.org/gcc-10/changes.html avr
https://gcc.gnu.org/wiki/avr-gcc Libf7
浮点数固有的舍入和精度问题仍然存在。