8

最近,我正在阅读Herb Sutter 的 GOTW 的 Double or Nothing的帖子, 我对以下程序的解释感到有些困惑:

 int main()
 {
     double x = 1e8;
     while( x > 0 )
     {
        --x;
     }
 }

假设这段代码在某台机器上运行 1 秒。我同意这样的代码很愚蠢的观点。

但是,根据有关问题的解释,如果我们x从更改floatdouble,那么在某些编译器上,它将使计算机永远运行。解释基于标准中的以下引用。

引用 C++ 标准的第 3.9.1/8 节:

有三种浮点类型:float、double 和 long double。double 类型提供的精度至少与 float 一样,long double 类型提供的精度至少与 double 一样。float 类型的值集是 double 类型的值集的子集;double 类型的值集是 long double 类型的值集的子集。

代码的问题是:

如果将“double”更改为“float”,您预计需要多长时间?为什么?

以下是给出的解释:

它可能需要大约 1 秒(在特定的实现中,浮点数可能比双精度更快、一样快或稍慢),或者永远需要,这取决于浮点数是否可以准确地表示从 0 到 1e8 的所有整数值(包括在内)。

标准中的上述引用意味着可能存在可以用双精度表示但不能用浮点表示的值。特别是在一些流行的平台和编译器上,double 可以准确地表示 [0,1e8] 中的所有整数值,但 float 不能。

如果 float 不能准确表示从 0 到 1e8 的所有整数值怎么办?然后修改后的程序将开始倒计时,但最终会达到一个无法表示且 N-1 == N 的值 N(由于浮点精度不足)......和

我的问题是:

如果 float 都不能表示1e8,那么我们初始化的时候应该已经溢出了float x = 1e8;那我们怎么能让电脑永远运行呢?

我在这里尝试了一个简单的例子(虽然不是double但是int

#include <iostream>

int main()
{
   int a = 4444444444444444444;
   std::cout << "a " << a << std::endl;
   return 0;
}
It outputs: a -1357789412

这意味着如果编译器无法用int类型表示给定的数字,则会导致溢出。

那我是不是看错了?我错过了什么?正在x从更改doublefloat未定义的行为吗?

谢谢!

4

2 回答 2

9

关键词是“准确”。

float可以表示1e8,甚至是准确的,除非你有一个怪胎float类型。但这并不意味着它可以精确表示所有较小的值,例如,通常2^25+1 = 33554433需要 26 位精度,不能精确表示float(通常,具有 23+1 位精度),也不能精确表示2^25-1 = 33554431,需要 25位精度。

然后这两个数字都表示为2^25 = 33554432,然后

33554432.0f - 1 == 33554432.0f

会循环。(你会更早地遇到一个循环,但那个循环有一个很好的十进制表示;)

在整数算术中,你有x - 1 != xfor all x,但在浮点算术中没有。

请注意,即使float只有通常的 23+1 位精度,循环也可能会结束,因为标准允许以比类型更高的精度执行浮点计算,并且如果以足够高的精度执行计算(例如通常double的 52+1 位),每次减法都会改变x

于 2013-05-16T15:02:48.100 回答
0

试试这个简单的修改来计算连续 x 值的值。

#include <iostream>
using namespace std;

int main()
 {
     float x = 1e8;
     while( x > 0 )
     {
        cout << x << endl;
        --x;
     }
 }

在 float 的某些实现中,您会看到 float 的值停留在 1e8 或该区域。这是因为 float 存储数字的方式。浮点数不能(也不能以任何位限制表示)表示所有可能的十进制值,因此当您处理浮点数中的非常大的值时,您本质上是一个小数,提高了一定的幂。好吧,如果这个十进制值以最后一位丢失的值结束,则意味着它被四舍五入。你最终得到的是一个递减(然后备份)到自身的值。

于 2013-05-16T15:01:46.580 回答