双舍入错误,即使使用 DBL_DIG
Double rounding error, even when using DBL_DIG
我正在尝试使用步长 0.3 生成一个介于 -10 和 10 之间的随机数(尽管我希望这些是任意值)并且我遇到双精度浮点精度问题。 Float.h 的 DBL_DIG
是指不发生舍入错误的最小精度 [编辑:这是错误的,请参阅 Eric Postpischil 的评论以了解 DBL_DIG
] 的真实定义,但是当打印到这么多数字,我仍然看到舍入错误。
#include <stdio.h>
#include <float.h>
#include <stdlib.h>
int main()
{
for (;;)
{
printf("%.*g\n", DBL_DIG, -10 + (rand() % (unsigned long)(20 / 0.3)) * 0.3);
}
}
当我运行这个时,我得到这个输出:
8.3
-7
1.7
-6.1
-3.1
1.1
-3.4
-8.2
-9.1
-9.7
-7.6
-7.9
1.4
-2.5
-1.3
-8.8
2.6
6.2
3.8
-3.4
9.5
-7.6
-1.9
-0.0999999999999996
-2.2
5
3.2
2.9
-2.5
2.9
9.5
-4.6
6.2
0.799999999999999
-1.3
-7.3
-7.9
当然,一个简单的解决方案是 #define DBL_DIG 14
,但我觉得这会浪费准确性。为什么会发生这种情况,我该如何防止这种情况发生?这不是 Is floating point math broken? 的副本,因为我问的是 DBL_DIG
,以及如何找到不发生错误的最小准确度。
对于问题中的具体代码,我们可以在最后一刻使用整数值来避免过多的舍入误差:
printf("%.*g\n", DBL_DIG,
(-100 + rand() % (unsigned long)(20 / 0.3) * 3.) / 10.);
这是通过将原始表达式中的每一项乘以 10(−10 因为 −100 和 .3 变为 3)然后将整个表达式除以 10 得到的。所以我们关心的所有值都在分子1是整数,浮点数正好表示(在其精度范围内)。
由于将精确计算整数值,因此在最后除以 10 时将只有一个舍入误差,结果将是 double
最接近所需值的值。
How many digits should I print to in order to avoid rounding error in most circumstances? (not just in my example above)
仅仅使用更多数字并不是一般情况下的解决方案。在大多数情况下,避免错误的一种方法是非常详细地了解浮点格式和算术,然后认真细致地编写代码。这种方法通常是好的,但并不总是成功,因为它通常是由人类实施的,尽管做出了所有相反的努力,他们仍然会继续犯错误。
脚注
1 考虑 (unsigned long)(20 / 0.3)
是一个较长的讨论,涉及对其他值和案例的意图和概括。
generate a random number between -10 and 10 with step 0.3
I would like the program to work with arbitrary values for the bounds and step size.
Why is this happening ....
问题的根源在于假设 typcial 实数(例如 string "0.3")可以精确编码为 double
.
一个double
可以精确编码大约264个不同的值。 0.3 不是其中之一。
而是使用最近的 double
。
精确 值和最接近的 2 个如下所列:
0.29999999999999993338661852249060757458209991455078125
0.299999999999999988897769753748434595763683319091796875 (best 0.3)
0.3000000000000000444089209850062616169452667236328125
所以 OP 的代码正在尝试“-10 和 10,步长为 0.2999...”并打印出 "-0.0999999999999996"
和 "0.799999999999999"
比 更 正确"-0.1"
和 "0.8"
.
.... how do I prevent this happening?
以更有限的精度打印。
// reduce the _bit_ output precision by about the root of steps
#define LOG10_2 0.30102999566398119521373889472449
int digits_less = lround(sqrt(20 / 0.3) * LOG10_2);
for (int i = 0; i < 100; i++) {
printf("%.*e\n", DBL_DIG - digits_less,
-10 + (rand() % (unsigned long) (20 / 0.3)) * 0.3);
}
9.5000000000000e+00
-3.7000000000000e+00
8.6000000000000e+00
5.9000000000000e+00
...
-1.0000000000000e-01
8.0000000000000e-01
OP 的代码实际上并没有执行“步骤”,因为它暗示了一个步长为 0.3 的循环。上面的 digits_less
是基于重复的“步骤”,否则 OP 的等式保证大约减少 1 位小数。精度的最佳降低取决于估计所有计算的潜在累积误差从"0.3"
转换-->double 0.3
(1/2位),除法(1/2位),乘法(1/2位)和加法(更复杂的位)。
- 等待下一个可能支持十进制浮点数的 C 版本。
我正在尝试使用步长 0.3 生成一个介于 -10 和 10 之间的随机数(尽管我希望这些是任意值)并且我遇到双精度浮点精度问题。 Float.h 的 DBL_DIG
是指不发生舍入错误的最小精度 [编辑:这是错误的,请参阅 Eric Postpischil 的评论以了解 DBL_DIG
] 的真实定义,但是当打印到这么多数字,我仍然看到舍入错误。
#include <stdio.h>
#include <float.h>
#include <stdlib.h>
int main()
{
for (;;)
{
printf("%.*g\n", DBL_DIG, -10 + (rand() % (unsigned long)(20 / 0.3)) * 0.3);
}
}
当我运行这个时,我得到这个输出:
8.3
-7
1.7
-6.1
-3.1
1.1
-3.4
-8.2
-9.1
-9.7
-7.6
-7.9
1.4
-2.5
-1.3
-8.8
2.6
6.2
3.8
-3.4
9.5
-7.6
-1.9
-0.0999999999999996
-2.2
5
3.2
2.9
-2.5
2.9
9.5
-4.6
6.2
0.799999999999999
-1.3
-7.3
-7.9
当然,一个简单的解决方案是 #define DBL_DIG 14
,但我觉得这会浪费准确性。为什么会发生这种情况,我该如何防止这种情况发生?这不是 Is floating point math broken? 的副本,因为我问的是 DBL_DIG
,以及如何找到不发生错误的最小准确度。
对于问题中的具体代码,我们可以在最后一刻使用整数值来避免过多的舍入误差:
printf("%.*g\n", DBL_DIG,
(-100 + rand() % (unsigned long)(20 / 0.3) * 3.) / 10.);
这是通过将原始表达式中的每一项乘以 10(−10 因为 −100 和 .3 变为 3)然后将整个表达式除以 10 得到的。所以我们关心的所有值都在分子1是整数,浮点数正好表示(在其精度范围内)。
由于将精确计算整数值,因此在最后除以 10 时将只有一个舍入误差,结果将是 double
最接近所需值的值。
How many digits should I print to in order to avoid rounding error in most circumstances? (not just in my example above)
仅仅使用更多数字并不是一般情况下的解决方案。在大多数情况下,避免错误的一种方法是非常详细地了解浮点格式和算术,然后认真细致地编写代码。这种方法通常是好的,但并不总是成功,因为它通常是由人类实施的,尽管做出了所有相反的努力,他们仍然会继续犯错误。
脚注
1 考虑 (unsigned long)(20 / 0.3)
是一个较长的讨论,涉及对其他值和案例的意图和概括。
generate a random number between -10 and 10 with step 0.3
I would like the program to work with arbitrary values for the bounds and step size.
Why is this happening ....
问题的根源在于假设 typcial 实数(例如 string "0.3")可以精确编码为 double
.
一个double
可以精确编码大约264个不同的值。 0.3 不是其中之一。
而是使用最近的 double
。
精确 值和最接近的 2 个如下所列:
0.29999999999999993338661852249060757458209991455078125
0.299999999999999988897769753748434595763683319091796875 (best 0.3)
0.3000000000000000444089209850062616169452667236328125
所以 OP 的代码正在尝试“-10 和 10,步长为 0.2999...”并打印出 "-0.0999999999999996"
和 "0.799999999999999"
比 更 正确"-0.1"
和 "0.8"
.
.... how do I prevent this happening?
以更有限的精度打印。
// reduce the _bit_ output precision by about the root of steps #define LOG10_2 0.30102999566398119521373889472449 int digits_less = lround(sqrt(20 / 0.3) * LOG10_2); for (int i = 0; i < 100; i++) { printf("%.*e\n", DBL_DIG - digits_less, -10 + (rand() % (unsigned long) (20 / 0.3)) * 0.3); } 9.5000000000000e+00 -3.7000000000000e+00 8.6000000000000e+00 5.9000000000000e+00 ... -1.0000000000000e-01 8.0000000000000e-01
OP 的代码实际上并没有执行“步骤”,因为它暗示了一个步长为 0.3 的循环。上面的 digits_less
是基于重复的“步骤”,否则 OP 的等式保证大约减少 1 位小数。精度的最佳降低取决于估计所有计算的潜在累积误差从"0.3"
转换-->double 0.3
(1/2位),除法(1/2位),乘法(1/2位)和加法(更复杂的位)。
- 等待下一个可能支持十进制浮点数的 C 版本。