我正在编写代码来实现袖珍计算器,我的应用程序的要求之一是将用户与浮点怪癖隔离开来,例如
sin(M_PI)
不返回0。
由于我是为 ARM 皮质 M0 编写此内容,因此内存有限,这意味着没有像二进制编码的十进制那样奇特的东西,我必须坚持使用双精度数。我使用 newlib 作为数学库,它具有我需要的所有功能。
为了保护用户免受浮点实现问题(例如上面指出的问题)的影响,我提出的一种解决方案是修改 sin() 并检查简单情况,例如输入是
M_PI
的倍数。
例如:
double better_sin(double z) {
double pi_ratio = z / M_PI;
if (floor(pi_ratio) == pi_ratio) return 0;
else return sin(z);
}
完成工作。
我的问题是,这有什么陷阱吗?速度并不重要,因为涉及人和键盘,因此所需的响应时间在毫秒范围内。
我尝试了一个简单的基准测试,看看这是否会搞砸 M_PI 周围的事情,但它似乎至少具有与正常 sin() 相同的“精度”。
int main(void) {
double a;
double result;
printf("For input M_PI\n");
a = M_PI;
printf("\n");
result = sin(nextafter(a, 3));
printf("libc's sin(M_PI-): %.10e \n", result);
result = better_sin(nextafter(a, 3));
printf("better_sin(M_PI-): %.10e \n", result);
printf("\n");
result = sin(a);
printf("libc's sin(M_PI): %.10e \n", result);
result = better_sin(a);
printf("better_sin(M_PI): %.10e \n", result);
printf("\n");
result = sin(nextafter(a, 4));
printf("libc's sin(M_PI+): %.10e \n", result);
result = better_sin(nextafter(a, 4));
printf("better_sin(M_PI+): %.10e \n", result);
printf("\n\n");
a = 50e6 * M_PI;
printf("\n");
result = sin(nextafter(a, 3));
printf("libc's sin(50E6 * M_PI-): %.10e \n", result);
result = better_sin(nextafter(a, 3));
printf("better_sin(50E6 * M_PI-): %.10e \n", result);
printf("\n");
result = sin(a);
printf("libc's sin(50E6 * M_PI): %.10e \n", result);
result = better_sin(a);
printf("better_sin(50E6 * M_PI): %.10e \n", result);
printf("\n");
result = sin(nextafter(a, 4));
printf("libc's sin(50E6 * M_PI+): %.10e \n", result);
result = better_sin(nextafter(a, 4));
}
返回:
libc's sin(M_PI-): 5.6655388976e-16
better_sin(M_PI-): 5.6655388976e-16
libc's sin(M_PI): 1.2246467991e-16
better_sin(M_PI): 0.0000000000e+00
libc's sin(M_PI+): -3.2162452994e-16
better_sin(M_PI+): -3.2162452994e-16
libc's sin(M_PI-): -4.9343786466e-08
better_sin(M_PI-): -4.9343786466e-08
libc's sin(M_PI): -1.9541464078e-08
better_sin(M_PI): -1.9541464078e-08
libc's sin(M_PI+): -4.9343786466e-08
better_sin(M_PI+): -4.9343786466e-08
这将需要编写大量代码,以确保科学计算器的所有功能都能为琐碎的情况返回正确的答案(如上所示),因此,如果我遗漏了更大的东西,任何输入都会受到赞赏。
这种努力基本上是徒劳的。没有一个数值系统能够完全实现实数运算,甚至不能实现任意精度运算或符号运算。
检查
M_PI
的倍数将会失败,因为 π 是无理数(且是超越数)。 M_PI
无法捕获 π 的完整值,因此它的倍数与 π 的倍数越来越远,而其他浮点值将比 M_PI
最接近的倍数更接近 π 的倍数。
此外,您还必须拼凑许多其他数值算术工件,例如
1/49*49-1
不产生零。
您需要重新考虑“用户与浮点怪癖隔离……”的要求,要么放弃它,要么完全准确地说明它是什么,充分考虑数值算术的属性可以实现什么。