Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/135.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用;“双”字;作为循环中的计数器变量_C#_C++_Loops_Floating Point_Counter - Fatal编程技术网

C# 使用;“双”字;作为循环中的计数器变量

C# 使用;“双”字;作为循环中的计数器变量,c#,c++,loops,floating-point,counter,C#,C++,Loops,Floating Point,Counter,在我目前正在阅读的一本书中,有以下摘录: 也可以使用浮点值 值作为循环计数器。这是一个 具有这种类型的循环的示例 柜位编号: double a(0.3), b(2.5); for(double x = 0.0; x <= 2.0; x += 0.25) cout << "\n\tx = " << x << "\ta*x + b = " << a*x + b; 这个循环的目的是 当值变化时,输出x 从0.0到1.0;但是,0.2 没有

在我目前正在阅读的一本书中,有以下摘录:

也可以使用浮点值 值作为循环计数器。这是一个 具有这种类型的循环的
示例
柜位编号:

double a(0.3), b(2.5);
for(double x = 0.0; x <= 2.0; x += 0.25)
    cout << "\n\tx = " << x << "\ta*x + b = " << a*x + b;
这个循环的目的是 当值变化时,输出
x
0.0
1.0
;但是,
0.2
没有精确的表示形式 二进制浮点值,因此
x
的值从来都不是精确的
1
。 因此,第二个回路控制 表达总是错误的,而 循环无限期地继续


有人能解释一下第一个代码块是如何运行的,而第二个代码块却没有运行吗?

第一个代码块最终将终止,即使
x
没有精确达到2.0。。。因为它最终会大于2.0,从而爆发

第二个必须使
x
正好达到1.0才能中断


不幸的是,第一个示例使用了0.25的步长,这完全可以用二进制浮点表示——如果两个示例都使用0.2作为步长,则会更明智。(0.2不能用二进制浮点精确表示。)

第一个块使用小于或等于条件(
这是一个更广泛问题的示例-在比较双精度时,通常需要检查是否在可接受的公差范围内相等,而不是完全相等

在某些情况下,通常检查未更改的默认值,相等即可:

double x(0.0); 
// do some work that may or may not set up x

if (x != 0.0) {   
    // do more work 
}
但是,通常情况下,无法通过这种方式检查预期值-您需要以下内容:

double x(0.0); 
double target(10000.0);
double tolerance(0.000001);
// do some work that may or may not set up x to an expected value

if (fabs(target - x) < tolerance) {   
    // do more work 
}
double x(0.0);
双目标(10000.0);
双公差(0.000001);
//执行一些可能会或可能不会将x设置为预期值的工作
if(fabs(target-x)<容差){
//多做点工作
}

浮点数在内部表示为二进制数,几乎总是采用IEEE格式。您可以在此处看到数字的表示方式:

例如,二进制中的0.25是0.01b,表示为+1.00000000000000000000000*2-2

它在内部存储,1位表示符号,8位表示指数(表示-127和+128之间的值),23位表示值(不存储前导的1)。实际上,这些位是:

[0][0111101][00000000000000000000000]

而二进制中的0.2没有精确的表示,就像1/3没有十进制中的精确表示一样

这里的问题是,正如1/2可以精确地用十进制格式表示为0.5,但1/3只能近似为0.3333,0.25可以精确地表示为二进制分数,但0.2不能。在二进制中,最后四位数字重复的位置是0.00100110011001100…b

要存储在计算机上,它被近似为0.00100110011001101b。这非常非常接近,所以如果你在计算坐标或任何其他绝对值重要的东西,它是可以的

不幸的是,如果您将该值加五次,您将得到1.00000000000000001B。(或者,如果您将0.2四舍五入到0.0010011001100B,您将得到0.11111100B)


无论哪种方式,如果循环条件为1.000000000000000001b==1.00000000000000000000000b,它都不会终止。如果使用一般做法是不比较两个浮点数,即:

// using System.Diagnostics;

double a = 0.2; a *= 5.0;
double b = 1.0;
Debug.Assert(a == b);
由于浮点数的不精确性,
a
可能不完全等于
b
。要进行相等性比较,可以将两个数字的差值与公差值进行比较:

Debug.Assert(Math.Abs(a - b) < 0.0001);
Debug.Assert(Math.Abs(a-b)<0.0001);

该示例存在多个问题,两种情况之间有两个不同之处

  • 涉及浮点相等的比较需要领域的专家知识,因此使用
    进行循环控制更安全

  • 循环增量
    0.25
    实际上没有确切的表示形式

  • 循环增量
    0.2
    没有精确表示

  • 因此,可以精确检查多个0.25(或1.0)增量的总和,但即使是单个0.2增量也不可能精确匹配

经常引用的一条一般规则是:不要对浮点数进行相等比较。虽然这是一条很好的一般建议,但在处理整数或由½+¼组成的整数加分数时,您可以期望得到精确的表示


你问了为什么?简短的回答是:因为分数表示为½+¼…,大多数十进制数没有精确的表示,因为它们不能被分解为二的幂。这意味着FP内部表示是长串位,将四舍五入到输出的预期值,但实际上不完全是value.

还请注意,在第一个示例中,您无法确定当x达到2.0时是否会执行循环体。这可能很重要,但您最好使用定点数字。“有人能解释一下第一个代码块是如何运行的,而第二个代码块不是吗?”是的,有人可以而且已经:@stefaanv,你所说的通常是正确的,但在第一个例子中并不特别。因为
0
.25
2
都是可精确表示的(以IEEE754和任何其他合理的浮点格式),我们知道在最后的循环迭代中,
x==2.0
将是正确的。@stefaanv:一个更容易的修复方法(比切换到定点)是选择一个介于序列值之间的阈值。例如(双倍x=0.0;x阅读
我喜欢你的解释,但如何在
forDebug.Assert(Math.Abs(a - b) < 0.0001);