举例说明
假设已有float类型的大数组x[10000]
和float类型的y[10000]
,将x[i]
以向下取整的方式转为都是整数的float类型数组,结果放在y[i]
数组里。
ceil/floor/round/int
傻傻分不清楚? 先把浮点取整函数梳理如下表:
函数名 | 符号 | 功能 | 举例 |
---|---|---|---|
ceil | ⌈𝑦⌉ | 向上取整 | 正数:(4, 5] -> 5;负数:(-6, -5] -> -5 |
floor | ⌊𝑦⌋ | 向下取整 | 正数:[5, 6) -> 5;负数:[-5, -4) -> -5 |
round | ⌊𝑦⌉ | 圆整,四舍五入 | 正数:[4.5, 5.5) -> 5;负数: (-5.5, -4.5] -> -5 |
int | NA | 截断 | 正数:[5, 6) -> 5;负数:(-6, -5] -> -5 |
思路分析
从上表中可以看出,floor函数的特性就是,非负数时等价于截断功能,负数时若不是整数则减一再截断,若是负整数则等于自身。我们容易发现,floor函数处理前后两数间距不可能>=1
,作为处理特殊情况负整数的依据。
于是有以下思路:
- 先判断是否为负数
- 若为非负数,则直接截断
- 若为负数,则统一都减一后再截断
- 处理特殊情况:负整数为其本身
- 当前结果统一加1,与初始float数据比较
- 若初始数据大于等于当前结果,则说明当前间距
>=1
,该值为负整数,需换为初始数据
注意,该思路同样也可以推广到ceil和round函数的优化。ceil处理前后数据间距小于1,round函数处理后数据间距不超过0.5。
代码实现
原函数:
#include <math.h>
int i;
for (i = 0; i < 10000; i++)
{
y[i] = (int)floor(x[i]);
}
优化后:
#include <math.h>
#include <arm_neon.h>
float32x4_t vf32x4Temp, vf32x4TempV1;
float32x4_t vf32x4Zero, vf32x4One;
int32x4_t vs32x4Temp;
uint32x4_t vu32x4TempV1;
vf32x4Zero = vdupq_n_f32(0.0f);
vf32x4One = vdupq_n_f32(1.0f);
#define STEP_NUM 4
for (i = 0; i < 10000 - STEP_NUM + 1; i += STEP_NUM) {
vf32x4Temp = vld1q_f32(&x[i]);
vf32x4TempV1 = vf32x4Temp;
vu32x4TempV1 = vcltq_f32(vf32x4Temp, vf32x4Zero); // 判断是否小于0
vf32x4Temp = vbslq_f32(vu32x4TempV1, vsubq_f32(vf32x4Temp, vf32x4One), vf32x4Temp); // 大于0,则截断;否则,就减1
vs32x4Temp = vcvtq_s32_f32(vf32x4Temp); // 截断取整,但存在如果是负数且为整数时,多减了1
vf32x4Temp = vcvtq_f32_s32(vs32x4Temp);
vu32x4TempV1 = vcgeq_f32(vf32x4TempV1, vaddq_f32(vf32x4Temp, vf32x4One)); // a>=b+1 时,多减1的负数整数本身
vf32x4Temp = vbslq_f32(vu32x4TempV1, vf32x4TempV1, vf32x4Temp); // 出现a>=b+1情况,必然是负数整数本身,此时赋值为原初始值
vst1q_f32(&y[i], vf32x4Temp);
}
for (; i < 10000; i++) // 处理数组尾部不足STEP_NUM的数据
{
y[i] = (float)floor(x[i]);
}