在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.0
。 last
包含一个值,当加到1111e28
产生+Inf
,因此接近float
的上限。
1111e28
选择合理地快速到达+Inf
; 它还需要足够大,以便当添加到大值时,循环继续进行,即它至少与最大和第二大非无限float
值之间的间隙一样大。
OP:…为什么作者在fltest
变量中添加了fltest
?
答:[编辑]对于使用float
, 1111e28
或1.111e31
工作的代码,此delta值需要仔细选择。 它应该足够大,如果fltest
是FLT_MAX
, fltest + 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_MAX
和FLT_EPSILON
以确保这一点。
这个问题是C没有定义范围FLT_MAX
和DBL_MAX
除了它们必须至少为 1E+37
。 因此,如果最大值非常大,则增量值1111e28或1111e297将不起作用。 示例: dbltest = dbltest + 1111e297;
,对于dbltest = 1e400
肯定不会增加1e400,除非dbltest
是一百个十进制数字的精度。
如果DBL_MAX
小于1111e297,则该方法也会失败。 注意:在2014年的简单平台上,找到double
和float
是相同的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评论
[编辑]
float
和double
的精度用“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_MAX
和FLT_EPSILON
以进行此确定。 如果提前知道这些值,那么代码简单可能是:
printf("Maximum range of float variable: %e\n", FLT_MAX);
float
可表示的最大值为3.40282e + 38。 选择常数1111e28,使得将该常数加到10 ^ 38范围内的数字仍然会产生不同的浮点值,这样fltest
的值将随着函数的运行而继续增加。 它需要足够大,以至于在10 ^ 38范围内仍然很重要,并且足够小以至于结果将是准确的。