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

发布于 2024-11-17 22:22:26 字数 900 浏览 3 评论 0原文

我现在正在读的一本书里,有这样一段摘录:

您还可以使用浮点数 作为循环计数器的值。这是一个 这种类型的 for 循环示例 计数器:

双倍 a(0.3), b(2.5);
for(双 x = 0.0; x <= 2.0; x += 0.25)
    计算<< "\n\tx = " << x << "\ta*x + b = " << a*x + b;

此代码片段计算 a*x+b 的值对应于 x 的值 从 0.02.0,步骤为 0.25;但是,你需要小心 当使用浮点计数器时 一个循环。许多小数值不能 精确地以二进制表示 浮点形式,所以有差异 可以累积值。 这意味着您不应该编写 for 循环使得结束循环 取决于浮点循环 计数器达到精确值。为了 例如,下面的设计很糟糕 循环永远不会结束:

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    计算<< x;

这个循环的目的是 输出 x 变化的值 从0.01.0;但是,0.2 没有确切的表示 二进制浮点值,所以 x 的值永远不会恰好是 1。 因此,第二个循环控制 表达式总是假的,并且 循环无限期地继续。

有人可以解释一下第一个代码块如何运行而第二个代码块不运行吗?

In a book I am currently reading, there is this excerpt:

You can also use a floating-point
value as a loop counter. Here's an
example of a for loop with this kind
of 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;

This code fragment calculates the
value of a*x+b for values of x
from 0.0 to 2.0, in steps of
0.25; however, you need to take care
when using a floating-point counter in
a loop. Many decimal values cannot be
represented exactly in binary
floating-point form, so discrepancies
can build up with cumulative values.
This means that you should not code a
for loop such that ending the loop
depends on a floating-point loop
counter reaching a precise value. For
example, the following poorly-designed
loop never ends:

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    cout << x;

The intention with this loop is to
output the value of x as it varies
from 0.0 to 1.0; however, 0.2
has no exact representation as a
binary floating-point value, so the
value of x is never exactly 1.
Thus, the second loop control
expression is always false, and the
loop continues indefinitely.

Can someone please explain how the first code block runs while the second doesn't?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

南街女流氓 2024-11-24 22:22:26

第一个最终将终止,即使 x 没有完全达到 2.0...因为它最终大于 2.0 ,从而爆发。

第二个必须让 x 恰好达到 1.0 才能中断。

不幸的是,第一个示例使用的步长为 0.25,这完全可以用二进制浮点表示 - 让两个示例都使用 0.2 作为步长会更明智。 (0.2 不能完全用二进制浮点数表示。)

The first one will eventually terminate, even if x doesn't reach exactly 2.0... because it'll end up being greater than 2.0, and thus break out.

The second one would have to make x hit exactly 1.0 in order to break.

It's unfortunate that the first example uses a step of 0.25, which is exactly representable in binary floating point - it would have been smarter to make both examples use 0.2 as the step size. (0.2 isn't exactly representable in binary floating point.)

心作怪 2024-11-24 22:22:26

第一个块使用小于或等于条件 (<=)。

即使浮点不准确,这最终也会是错误的。

The first block uses a less-than-or-equal condition (<=).

Even with floating-point inaccuracy, that will eventually be false.

許願樹丅啲祈禱 2024-11-24 22:22:26

这是一个更广泛问题的示例 - 在比较双精度数时,您通常需要在可接受的容差范围内检查是否相等,而不是完全相等。

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

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 
}

This is an example of a broader issue - when comparing doubles, you often need to check for equality within some acceptable tolerance rather than exact equality.

In some cases, typically checking for an unchanged default value, equality is fine:

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

if (x != 0.0) {   
    // do more work 
}

In general though, checking versus an expected value cannot be done that way - you would need something like:

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 
}
沉睡月亮 2024-11-24 22:22:26

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

http://babbage.cs.qc.edu/IEEE-754/

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

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

[0][01111101][00000000000000000000000]

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

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

要存储在计算机上,它会四舍五入为 0.0010011001100110011001101b,这非常非常接近,所以如果您正在计算坐标或任何其他绝对值很重要的东西,不幸的是

,如果您将该值添加五次,您将得到 1.00000000000001b(或者,如果您将 0.2 向下舍入为 0.0010011001100110011001100b,则您将得到会得到0.11111111111111111111100b)

无论哪种方式,如果循环条件是 1.00000000000000000000001b==1.00000000000000000000000b ,它都不会终止。如果您使用 <= 代替,如果该值刚好低于最后一个值,则可能会额外运行一次,但它会停止。

可以制定一种可以准确表示小十进制值的格式(例如任何只有两位小数的值)。它们用于金融计算等。但正常的浮点值确实是这样工作的:它们用表示一些小的“简单”数字(如 0.2)的能力来换取以一致的方式表示广泛范围的能力。

由于这个确切原因,通常会避免使用浮点数作为循环计数器,常见的解决方案是:

  • 如果一次额外的迭代无关紧要,请使用 <=
  • 如果确实重要,请改为使用条件 <=1.0001,或者某些其他值小于您的增量,因此 off-by-0.0000000000000000000001 错误并不重要
  • 使用整数并在循环期间将其除以某个值
  • 使用专门制作的类准确地表示小数值

编译器可以优化浮点“=”循环,将其转换为您想要发生的情况,但我不知道标准是否允许或在实践中是否发生过这种情况。

Floating point numbers are represented internally as a binary number, almost always in IEEE format You can see how numbers are represented here:

http://babbage.cs.qc.edu/IEEE-754/

For instance, 0.25 in binary is 0.01b and is represented as +1.00000000000000000000000 * 2-2.

This is stored internally with 1 bit for the sign, eight bits for the exponent (representing a value between -127 and +128, and 23 bits for the value (the leading 1. is not stored). In fact, the bits are:

[0][01111101][00000000000000000000000]

Whereas 0.2 in binary has no exact representation, just like 1/3 has no exact representation in decimal.

Here the problem is that just as 1/2 can be represented exactly in decimal format as 0.5, but 1/3 can only be approximated to 0.3333333333, 0.25 can be represented exactly as a binary fraction, but 0.2 cannot. In binary it is 0.0010011001100110011001100....b where the last four digits repeat.

To be stored on a computer it is roudned to 0.0010011001100110011001101b. Which is really, really close, so if you're calculating coordinates or anything else where absolute values matter, it's fine.

Unfortunately, if you add that value to itself five times, you will get 1.00000000000000000000001b. (Or, if you had rounded 0.2 down to 0.0010011001100110011001100b instead, you would get 0.11111111111111111111100b)

Either way, if your loop condition is 1.00000000000000000000001b==1.00000000000000000000000b it will not terminate. If you use <= instead, it's possible it will run one extra time if the value is just under the last value, but it will stop.

It would be possible to make a format that can accurately represent small decimal values (like any value with only two decimal places). They are used in financial calculations, etc. But normal floating point values do work like that: they trade the ability to represent some small "easy" numbers like 0.2 for the ability to represent a wide range in a consistent fashion.

It's common to avoid using a float as a loop counter for that exact reason, common solutions would be:

  • If one extra iteration doesn't matter, use <=
  • If it does matter, make the condition <=1.0001 instead, or some other value smaller than your increment, so off-by-0.0000000000000000000001 errors don't matter
  • Use an integer and divide it by something during the loop
  • Use a class specially made to represent fractional values exactly

It would be possible for a compiler to optimise a float "=" loop to turn it into what you mean to happen, but I don't know if that's permitted by the standard or ever happens in practice.

墨小墨 2024-11-24 22:22:26

该示例存在多个问题,并且案例之间有两点不同。

  • 需要该领域的专业知识,因此使用 <> 进行循环控制会更安全。

  • 循环增量0.25实际上确实有一个精确的表示

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

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

经常引用的一般规则是:不要对浮点数进行相等比较。虽然这是一个很好的一般建议,但在处理整数或整数加由 1/2 + 1/4 组成的分数时...您可以期待准确的表述。

你问为什么?简短的答案是:因为分数表示为 1/2 + 1/4 ...,所以大多数十进制数字没有精确的表示形式,因为它们无法分解为 2 的幂。这意味着 FP 内部表示是长位字符串,它们将舍入为输出的预期值,但实际上并不完全是该值。

There are multiple issues with the example, and two things are different between the cases.

  • A comparison involving floating point equality requires expert knowledge of the domain, so it's safer to use < or > for loop controls.

  • The loop increment 0.25 actually does have an exact representation

  • The loop increment 0.2 does not have an exact representation

  • Consequently, it's possible to exactly check for the sum of many 0.25 (or 1.0) increments but it isn't possible to exactly match even a single 0.2 increment.

A general rule is often cited: don't make equality comparisons of floating point numbers. While this is good general advice, when dealing with integers or integers plus fractions composed of ½ + ¼ ... you can expect exact representations.

And you asked why? The short answer is: because fractions are represented as ½ + ¼ ..., most decimal numbers don't have exact representations since they cannot be factored into powers of two. This means the FP internal representations are long strings of bits that will round to an expected value for output but not actually be exactly that value.

一抹淡然 2024-11-24 22:22:26

一般做法是不比较两个浮点数,即:

// 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);

General practice is that that you do not compare two floating point numbers, i.e.:

// using System.Diagnostics;

double a = 0.2; a *= 5.0;
double b = 1.0;
Debug.Assert(a == b);

Because of the imprecision of floating point numbers, a might not exactly be equal to b. To compare for equality you might compare the difference of two numbers with a tolerance value:

Debug.Assert(Math.Abs(a - b) < 0.0001);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文