在K&R 2-1中解释此代码

我正在尝试确定各种浮点类型的范围。 当我读到这段代码时:

#include  main() { float fl, fltest, last; double dbl, dbltest, dblast; fl = 0.0; fltest = 0.0; while (fl == 0.0) { last = fltest; fltest = fltest + 1111e28; fl = (fl + fltest) - fltest; } printf("Maximum range of float variable: %e\n", last); dbl = 0.0; dbltest = 0.0; while (dbl == 0.0) { dblast = dbltest; dbltest = dbltest + 1111e297; dbl = (dbl + dbltest) - dbltest; } printf("Maximum range of double variable: %e\n", dblast); return 0; } 

我不明白为什么作者在fltest变量中添加了fltest

fltest达到+Inf ,循环终止,因为此时fl = (fl + fltest) - fltest变为NaN ,不等于0.0last包含一个值,当加到1111e28产生+Inf ,因此接近float的上限。

1111e28选择合理地快速到达+Inf ; 它还需要足够大,以便当添加到大值时,循环继续进行,即它至少与最大和第二大非无限float值之间的间隙一样大。

OP:…为什么作者在fltest变量中添加了fltest
答:[编辑]对于使用float1111e281.111e31工作的代码,此delta值需要仔细选择。 它应该足够大,如果fltestFLT_MAXfltest + delta的总和将溢出并变为float.infinity 。 使用舍入到最近模式,这是FLT_MAX*FLT_EPSILON/4 。 在我的机器上:

 min_delta 1.014120601e+31 1/2 step between 2nd largest and FLT_MAX FLT_MAX 3.402823466e+38 FLT_EPSILON 8.388608000e+06 FLT_MAX*FLT_EPSILON 4.056481679e+31 

delta需要足够小,所以如果f1test是第二大数字,则添加delta, 不会求和float.infinity并跳过FLT_MAX 。 这是3x min_delta

 max_delta 3.042361441e+31 

所以1.014120601e+31 <= 1111e28 < 3.042361441e+31

@ david.pfx是的。 1111e28是一个可爱的数字,它在范围内。

注意:当数学及其中间值出现并发症时,即使变量是float也可能会计算出更高的精度,例如double 。 这在C和控制中是允许的,通过FLT_EVAL_METHOD非常仔细的编码。


1111e28是一个好奇的值,如果作者都准备好了解FLT_MAX的一般范围, FLT_MAX这个值是FLT_MAX

以下代码预计会循环多次(在一个测试平台上为24946069)。 希望价值fltest最终变得“无限”。 然后f1将变为NaN作为Infinity - Infinity的差异。 while循环以Nan!= 0.0结束。 @ecatmur

 while (fl == 0.0) { last = fltest; fltest = fltest + 1111e28; fl = (fl + fltest) - fltest; } 

循环,如果以足够小的增量完成,将得出精确的答案。 需要事先了解FLT_MAXFLT_EPSILON以确保这一点。

这个问题是C没有定义范围FLT_MAXDBL_MAX除了它们必须至少为 1E+37 。 因此,如果最大值非常大,则增量值1111e28或1111e297将不起作用。 示例: dbltest = dbltest + 1111e297; ,对于dbltest = 1e400肯定不会增加1e400,除非dbltest是一百个十进制数字的精度。

如果DBL_MAX小于1111e297,则该方法也会失败。 注意:在2014年的简单平台上,找到doublefloat是相同的4字节IEEE 二进制32并不奇怪32 )第一次通过循环, dbltest变为无穷大并且循环停止,报告“双变量的最大范围: 0.000000e + 00" 。

有许多方法可以有效地导出最大浮点值。 下面的示例使用随机初始值来帮助显示其对潜在变体FLT_MAX弹性。

 float float_max(void) { float nextx = 1.0 + rand()/RAND_MAX; float x; do { x = nextx; nextx *= 2; } while (!isinf(nextx)); float delta = x; do { nextx = x + delta/2; if (!isinf(nextx)) { x = nextx; } delta /= 2; } while (delta >= 1.0); return x; } 

isinf()是一个新的C函数。 足够简单,如果需要可以自己滚动。

在re:@didierc评论

[编辑]
floatdouble的精度用“epsilon”表示:“1和最小值之间的差值大于1,可以在给定的浮点类型中表示......”。 最大值如下

 FLT_EPSILON 1E-5 DBL_EPSILON 1E-9 

Per @Pascal Cuoq评论。 “... 1111e28被选择为大于FLT_MAX * FLT_EPSILON。”,1111e28需要至少FLT_MAX*FLT_EPSILON来影响循环的加法,但小到足以精确地达到无穷大之前的数字。 同样,需要事先了解FLT_MAXFLT_EPSILON以进行此确定。 如果提前知道这些值,那么代码简单可能是:

 printf("Maximum range of float variable: %e\n", FLT_MAX); 

float可表示的最大值为3.40282e + 38。 选择常数1111e28,使得将该常数加到10 ^ 38范围内的数字仍然会产生不同的浮点值,这样fltest的值将随着函数的运行而继续增加。 它需要足够大,以至于在10 ^ 38范围内仍然很重要,并且足够小以至于结果将是准确的。