对于在ESP32上运行的C程序中的计算,我必须以以下方式乘和除以下整数:150 × 10000 ÷ 155 ÷ 138 × 100 ÷ 220 × 100
,它为float变量生成3100.000000,为32位无符号整数生成3100。
我尝试使用以下代码测试https://www.onlinegdb.com/上的计算结果:
int main () {
float calc = 150 * 10000 / 155 / 138 * 100 / 220 * 100 ;
printf ( "calc = %f\n", calc ) ; // 3100.000000
uint32_t calc0 = 150 * 10000 / 155 / 138 * 100 / 220 * 100 ;
printf ( "calc0 = %u\n", calc0 ) ; // 3100
}
其再次产生3100.000000用于浮点数,且3100用于32位无符号整数。
如果我在我的掌上电脑或笔记本电脑上的计算器中输入相同的数字,结果在两种情况下都是3187,555782226。
所以,我在ESP32上有一个准确性损失(如果我没有搞砸公式的话)。(3187−3100)÷3187×100 ~= 2,73 %
差异来自哪里以及如何产生,是否可以在32位微控制器上获得与PC上相同的准确结果?
4条答案
按热度按时间xqkwcwgp1#
您不会在计算器或移动设备上丢失任何精度。The precise result is
3187.5557822261889583067701...
和您的移动设备是近似相当准确。问题是在表达式
150 * 10000 / 155 / 138 * 100 / 220 * 100
中,所有的乘法和除法都是在整数之间进行的。即使您使用这个表达式来初始化float
,也为时已晚;精度已经丧失。为了获得更精确的结果,通过添加
.f
后缀使第一个操作数为float
,所有操作都将在浮点数之间进行,然后:图纸:
1)除了
/ 155 / 138 / 120
,我们还可以写/ (155ull * 138 * 120)
。The result is guaranteed to be the same。htrmnn0y2#
如果你的表达式只有
int
项,它会进行int
计算,不管它是否被赋值给float
(1)。* 赋值 * 对计算本身没有影响。例如,子表达式
150 * 10000 / 155
将给予精确的值9677
,而不是更精确的值(约为9677.419
)。你需要 * 告诉 * 它以浮点方式进行计算,这可以通过简单地将第一项设置为
150.0
而不是150
来完成。请记住,这实际上将按照
double
进行计算(比float
的范围和精度更高)。然后,当将其分配给float
目标时,将进行任何精度损失调整。如果这是您的平台(可能是有限的)的问题,您仍然可以通过使用150f
(2)在计算中使用float
。这也是为什么你的“使用
uint64_t
并先做乘法”(在你的一条评论中)在这里没有帮助。这只影响 final 类型,计算仍然是用int
类型完成的,因此不会避免任何溢出。同样,您可以通过在表达式中使用类似(uint64_t)150
而不是150
来解决这个问题。(1)表达式
operand1 operation operand2
的一般规则是,两个操作数首先被修改为具有相同的“公共真实的类型”。因此,例如,两个
int
操作数将保持int
。对于int
和long
,int
将转换为long
。结果则是相同类型。这在ISO(C17)标准的6.3.1 Arithmetic operands
和6.3.1.8 Usual arithmetic conversions
下涵盖。前者给出了各种整数类型的秩,后者详细解释了如何执行转换。重要的是,
float
和int
在进行计算之前会将后者“升级”为float
。(2)在不同的网络位置有很多关于EPS 32上浮点性能的讨论,因此
double
操作可能会有问题,具体取决于您的需求。但是,与所有优化问题一样,您应该“测量,而不是猜测”。然后,您可以明智地做出成本/效益决策,在速度和范围/精度之间进行选择。jucafojl3#
整数除法将值向零截断。在
150 * 10000 / 155
的情况下,结果是9677.41935484
,但变成9677
,然后除以138
,这应该是70.126227209
,但变成70
。随后的乘法放大了误差的幅度。简而言之,整数除法需要精确度。可能这可以通过重新排序计算来减轻,最后进行除法,从而具有更少的乘法,“放大”错误。但要注意溢出。6tqwzwtp4#
在没有FPU的微控制器中,您通常希望坚持定点算法并手动设置精度。在没有FPU的情况下使用
float
意味着糟糕的程序性能,因为编译器将通过软件库来处理浮点。忘记这一点,停止接受PC程序员的坏建议。定点算术不是火箭科学-你需要知道整数限制,小学数学和一点常识:)基本上它的意思是:先乘后除,因为只有在除法过程中才会损失精度,而整数除法会截断所有小数。
在无符号32的情况下,上限是2^32 - 1,或者如果你愿意的话是42.9亿。最坏情况计算在任何时候都不应超过此值。
注意:所有整数常量都有一个类型,对于像
150
这样的常量,它是int
,它的符号是-32,实际上不是一个有用的类型。我们可以用u
或ul
后缀所有常量以确保正确的类型,或者我们可以确保其中一个操作数是正确的类型,其中隐式提升将提升另一个操作数。如果我们想要3位小数,那么我们只需乘以10^3,然后重新排列等式,这样我们就不会在任何地方达到上限:
输出:
3187.555
。当然,在每次除法时,你会损失一点精度,因为在这个例子中,计算的上限是3位小数。并且最终结果是截断的,而不是四舍五入的。如果精度很重要,那么您可以升级到64位算术。这在32位MCU上较慢,但它仍然比软件浮点快得多。事实证明,可能更准确:
输出:
3187.5557822261