4.1.4. 浮点数相关FAQ

4.1.4.1. C代码中能否正常使用浮点数

C代码中总是可以使用浮点数运算的。数学库libm也是支持的(tan,sin等函数所在的库)。区别在于:

  1. 有一些芯片没有硬件浮点(也就是指,单条硬件指令实现的浮点运算),这种情况下,编译器会生成软件实现的浮点运算(即libcompiler_rt.a库中的__adddf3之类的函数)。

  2. 有一些芯片支持单精度硬件浮点(也就是指,对于单精度float相关的运算,有单条指令硬件指令实现的浮点运算),这种情下,对于单精度运算,编译器会生成相关的指令。但是双精度浮点运算,仍然会使用软件实现的方式(例如,双精度浮点加法__adddf3)。

  3. 硬件浮点和软件浮点的差异主要在于运算速度和生成的代码大小。结果是一致的。软件实现的运算速度更慢、代码更大。

4.1.4.2. 哪些芯片支持单精度硬件浮点

目前只有部分 pi32v2 架构的芯片支持单精度硬件浮点。其中,r3,r4版本的指令集的芯片是支持的。也就是编译的时候,指定了-mcpu=r3,-mcpu=r4参数的芯片。

4.1.4.3. 为什么我的代码没有生成单精度硬件浮点指令

这个一般有多种情况

  1. 当前编译选项未指定-mcpu=r3参数。

  2. 如果是库,需要检查库编译时候,是否添加了-mcpu=r3参数。(注意,库和SDK可能会被程序员错误地设置成了不同的-mcpu参数,参考:在已经有了标准 SDK 工程的情况下,如何获得编译库的工程 获取与SDK参数保持一致的库工程)

  3. 代码本身并没有使用单精度浮点指令。

    1. 注意,浮点常数默认是double。在常数末尾加上f后,才是单精度。例如,1.0是一个double1.0f是一个float。后者才是单精度浮点数

    2. 当单精度和双精度混合运算的时候,会先把单精度提升为双精度再进行双精度运算。

4.1.4.4. 为什么我的代码中有双精度运算

一般情况下,我们会在仅支持硬件单精度浮点运算的芯片上,尽量避免使用双精度。但是有些情况下仍然会出现了双精度的运算,例如,发现调用了__adddf3之类的双精度软件实现函数。这主要有下面几点原因:

  1. 浮点常数默认是double。在常数末尾加上f后,才是单精度。例如,1.0是一个double1.0f是一个float。后者才是单精度浮点数。

  2. 当单精度和双精度混合运算的时候,会先把单精度提升为双精度再进行双精度运算。

  3. 当给带有可变长参数(即,...)的函数传参时,会提升为双精度。例如,对int printf(const char *fmt, ...);这样的函数,执行float a = 1.0f; printf("%f\n", a);的时候,a会被提升为double后再传递。 注意,如果函数未声明,默认参数列表是可边长的,参考:关于调用了未声明函数的警告

4.1.4.5. 为什么出现了浮点异常

芯片的浮点异常是可以打开或者关闭的。默认情况下,编译器会假设芯片没有开启浮点异常。也就是说,当出现浮点运算有问题的时候,只会产生NaN。例如,除零的时候,结果是NaN,但是不发生异常。

但是有时候为了调试,可能会打开浮点异常。这个时候,除零之类的错误将会导致异常中断。为了配合这种情况,编译参数需要对应加上-mllvm -floating-point-may-trap,链接参数需要对应加上--plugin-opt=-floating-point-may-trap。这两个参数的作用是告诉编译器浮点数运算可能会导致异常,有一些优化不能做。

4.1.4.6. 为什么浮点函数好像不正常