我最近遇到了一些代码,其中包含
形式的循环for (int i = 0; i < 1e7; i++){
}
我质疑这样做的智慧,因为1e7是浮点类型,并且在评估停止条件时会促使i
被提升。这应该引起关注吗?
答案 0 :(得分:50)
这里的大象是int
的范围小到-32767到+32767,以及分配时的行为这个int
的值大于此值 undefined 。
但是,至于你的要点,确实应该关注你,因为这是一个非常坏习惯。事情可能会出错,1e7是浮点式双重类型。
由于类型提升规则将i
转换为浮点这一事实有点没有实际意义:如果存在明显的整数文字的意外截断,则会造成真正的损害。通过&#34;示例证明&#34;,首先考虑循环
for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 18446744073709551615ULL; ){
std::cout << i << "\n";
}
这会输出范围内i
的每个连续值,正如您所期望的那样。请注意std::numeric_limits<std::uint64_t>::max()
为18446744073709551615ULL
,比2的第64次幂小1。(此处我使用类似幻灯片的&#34;运算符&#34; ++<
这在使用unsigned
类型时非常有用。许多人认为-->
和++<
是混淆的,但在科学编程中它们很常见,尤其是-->
。)
现在在我的机器上,double是一个IEEE754 64位浮点。 (例如,方案特别擅长于精确表示2的幂 - IEEE754可以精确地表示2到1022的幂。)因此18,446,744,073,709,551,616
(2的64次幂)可以完全表示为double。之前最接近的可表示数字是18,446,744,073,709,550,592
(减少1024)。
现在让我们把循环写成
for (std::uint64_t i = std::numeric_limits<std::uint64_t>::max() - 1024; i ++< 1.8446744073709551615e19; ){
std::cout << i << "\n";
}
在我的机器上只输出一个值i
:18,446,744,073,709,550,592
(我们已经看过的数字)。这证明1.8446744073709551615e19
是浮点类型。如果允许编译器将文字视为整数类型,则两个循环的输出将是等效的。
答案 1 :(得分:14)
假设您的int
至少为32位,它将起作用。
但是,如果你真的想使用指数表示法,最好在循环外定义一个整数常量并使用正确的强制转换,如下所示:
const int MAX_INDEX = static_cast<int>(1.0e7);
...
for (int i = 0; i < MAX_INDEX; i++) {
...
}
考虑到这一点,我说要写
要好得多const int MAX_INDEX = 10000000;
或者如果你可以使用C ++ 14
const int MAX_INDEX = 10'000'000;
答案 2 :(得分:10)
1e7
是double
类型的文字,通常double
是64位IEEE 754格式,带有52位尾数。大约每2的十次幂对应于10的三次幂,所以double
应该能够表示至少10 5 * 3 = 10 15 ,完全。如果int
是32位,那么int
大约有10 3 * 3 = 10 9 作为最大值(要求Google搜索它说&#34; 2 ** 31 - 1&#34; = 2 147 483 647,即粗略估计的两倍。
因此,在实践中,它在当前的桌面系统上更安全。
但C ++允许int
仅为16位,例如一个具有小int
的嵌入式系统,其中一个将具有未定义的行为。
答案 3 :(得分:2)
如果要循环一个精确的整数次迭代,例如,如果迭代数组中的所有元素,那么与浮点值进行比较可能不是一个好主意,仅仅是出于准确性的原因;因为将一个整数隐式转换为float会将整数截断为零,所以没有真正的越界访问危险,它只会中断循环。
现在问题是:这些影响何时开始?你的程序会体验它们吗?目前通常使用的浮点表示是IEEE 754.只要指数为0,浮点值基本上就是整数。对于尾数,C双精度浮点数为52位,这为整数精度提供了最多2 ^ 52的值,大约为1e15。如果不指定后缀f
,您希望浮点文字被解释为单精度,则文字将是双精度,隐式转换也将以此为目标。因此,只要您的循环结束条件少于2 ^ 52,它就可以可靠!
现在,您需要考虑x86架构的一个问题是效率。最初的80x87 FPU采用不同的封装,后来又采用了不同的芯片,因此在x86汇编级别上获取值进入FPU寄存器有点尴尬。根据您的意图,它可能会影响实时应用程序的运行时间;但那是不成熟的优化。
TL; DR:安全吗?肯定是的。它会引起麻烦吗?它可能会导致数值问题。它可以调用未定义的行为吗?取决于你如何使用循环结束条件,但如果i
用于索引数组,并且由于某种原因,数组长度最终在浮点变量中总是截断为零,则不会导致逻辑问题。这样做很聪明吗?取决于应用程序。