为什么下面的中间类型转换不准确?
Why is there inaccuracy in the following intermediate type conversion?
以下示例来自 发现现代 C++ 一书的第 14 页,Peter Gottschling。作者声明:
为了说明这种转换行为,让我们看下面的例子:
long l = 1234567890123;
long l2 = l + 1.0f - 1.0; // imprecise
long l3 = l + (1.0f - 1.0); // precise
这在作者的平台上导致:
l2 = 1234567954431;
l3 = 1234567890123;
我的问题是,究竟是什么导致了这种不精确?是不是因为加减法的左结合性,所以l2
被计算为(l + 1.0f) - 1.0
?如果是这样,float
(see) 的值范围 3.4E +/- 38 (7 digits)
肯定涵盖了值 1234567890123
,因此据我所知,缩小范围应该不是问题。
A float
通常是 32 位。与相同大小的 int
相比,您认为它如何实现更大的射程(最大值 ~3.4e38
),后者的最大值为 ~2.1e9
?
唯一可能的答案是它不能在通往最大值的路上存储一些整数。并且可表示数字之间的差距随着绝对值的增加而增加。
考虑这段代码:
#include <cmath>
#include <iostream>
#include <limits>
void foo(float x, int n)
{
while (n-- > 0)
{
std::cout << x << "\n "[n > 0];
x = std::nextafter(x, std::numeric_limits<float>::infinity());
}
}
int main()
{
std::cout.precision(1000);
foo(0.001, 3);
foo(1, 3);
foo(100000000, 3);
}
它以尽可能慢的速度遍历 float
值,即以尽可能小的数量增加值。
0.001000000047497451305389404296875 0.00100000016391277313232421875 0.001000000280328094959259033203125
1 1.00000011920928955078125 1.0000002384185791015625
100000000 100000008 100000016
如你所见,在100000000
附近只能表示每8个整数。
第一行
l2 = l + 1.0f
导致生成一个中间浮点数,并且 1234567890123 将不适合浮点数而不损失精度。然后减去 1。你的答案是正确的
第2行,不要写这个。在我看来,这是未定义的行为。你有一个来自括号中的浮点数,被添加到一个 int,它创建一个浮点数,然后在评估结束时被转换回一个 int。除非我读错了标准,否则它也应该给出不精确的值,尽管编译器优化可能会阻止它。
除非你想把自己弄糊涂,否则在添加时显式转换,如果这样的话
以下示例来自 发现现代 C++ 一书的第 14 页,Peter Gottschling。作者声明:
为了说明这种转换行为,让我们看下面的例子:
long l = 1234567890123;
long l2 = l + 1.0f - 1.0; // imprecise
long l3 = l + (1.0f - 1.0); // precise
这在作者的平台上导致:
l2 = 1234567954431;
l3 = 1234567890123;
我的问题是,究竟是什么导致了这种不精确?是不是因为加减法的左结合性,所以l2
被计算为(l + 1.0f) - 1.0
?如果是这样,float
(see) 的值范围 3.4E +/- 38 (7 digits)
肯定涵盖了值 1234567890123
,因此据我所知,缩小范围应该不是问题。
A float
通常是 32 位。与相同大小的 int
相比,您认为它如何实现更大的射程(最大值 ~3.4e38
),后者的最大值为 ~2.1e9
?
唯一可能的答案是它不能在通往最大值的路上存储一些整数。并且可表示数字之间的差距随着绝对值的增加而增加。
考虑这段代码:
#include <cmath>
#include <iostream>
#include <limits>
void foo(float x, int n)
{
while (n-- > 0)
{
std::cout << x << "\n "[n > 0];
x = std::nextafter(x, std::numeric_limits<float>::infinity());
}
}
int main()
{
std::cout.precision(1000);
foo(0.001, 3);
foo(1, 3);
foo(100000000, 3);
}
它以尽可能慢的速度遍历 float
值,即以尽可能小的数量增加值。
0.001000000047497451305389404296875 0.00100000016391277313232421875 0.001000000280328094959259033203125
1 1.00000011920928955078125 1.0000002384185791015625
100000000 100000008 100000016
如你所见,在100000000
附近只能表示每8个整数。
第一行
l2 = l + 1.0f
导致生成一个中间浮点数,并且 1234567890123 将不适合浮点数而不损失精度。然后减去 1。你的答案是正确的
第2行,不要写这个。在我看来,这是未定义的行为。你有一个来自括号中的浮点数,被添加到一个 int,它创建一个浮点数,然后在评估结束时被转换回一个 int。除非我读错了标准,否则它也应该给出不精确的值,尽管编译器优化可能会阻止它。
除非你想把自己弄糊涂,否则在添加时显式转换,如果这样的话