下面的程序进行一些计算,以计算需要多少个无限收敛和的项才能超过某个阈值。我了解/怀疑如果速率太大(例如750,请参阅下文),则这样的循环可能不会终止,因为浮点运算会导致计算不准确。
但是,下面的循环在调试模式下(Microsoft Visual Studio .net 4.6.1)输出i = 514,但在发布模式下不会终止(“挂起”)。也许更奇怪:如果我在循环内注释掉了“ if”部分(只是想弄清楚发生了什么),那么发布模式代码突然也会输出i = 514 。
这背后的原因是什么?如何避免在发布模式下弹出此类问题? (编辑:我宁愿不在生产代码中添加if语句或break语句;该代码应尽可能地具有高性能。)
static void Main(string[] args)
{
double rate = 750;
double d = 0.2;
double rnd = d * Math.Exp(rate);
int i = 0;
int j = 0;
double term = 1.0;
do
{
rnd -= term;
term *= rate;
term /= ++i;
//if (j++ > 1000000)
//{
// Console.WriteLine(d + " " + rate + " " + term);
// j = 0;
// Console.ReadLine();
//}
} while (rnd > 0);
Console.WriteLine("i= "+i);//do something with i
Console.ReadLine();
return;
}
答案 0 :(得分:4)
执行摘要,即使在Debug中,代码也被破坏了-即使循环退出,它也会产生错误的结果。您需要了解浮点运算的局限性。
如果您使用调试器逐步执行代码,则会很快发现问题所在。
Math.Exp(rate)
大。很大。大于双精度数可以容纳的数字。因此,rnd
以值Infinity
开始。
来到rnd -= term
时,该数字是Infinity
减去某个数字,该数字仍为Infinity
。因此rnd > 0
始终为true,因为Infinity
大于零。
这一直进行到term
也到达Infinity
为止。然后rnd -= term
成为Infinity - Infinity
的{{1}}。与NaN
相比的任何事物都是错误的,因此NaN
突然是错误的,并且循环退出。
我不知道为什么释放模式会发生这种变化(我无法重现),但是浮点运算的顺序很可能发生了变化。如果您同时处理大量数据,这可能会对输出产生巨大影响。例如,可以对rnd > 0
进行排序,以使term *= rate; term /= ++i
始终首先在Debug中发生,并且在除法发生之前乘法达到term * rate
。在发行版中,可能会重新排序,使得Infinity
首先发生,这使您无法再击中rate / ++i
。因为您开始时会遇到Infinity
始终为rnd
的错误,所以只有当Infinity
也是term
时,循环才会中断。
我怀疑这也可能取决于您的处理器等因素。
编辑:有关更好的解释,请参见@HansPassant的This answer。
同样,尽管挂起与未挂起之间的差异可能取决于Debug vs Release,但您的代码从一开始就没有起作用。即使在Debug中,它也会产生错误的结果!
如果要处理大数或小数,则需要注意双精度的限制。浮点数是复杂的野兽,并且具有许多微妙的行为。您需要注意这一点,例如,请参见以下著名文章:What Every Computer Scientist Should Know About Floating-Point Arithmetic。注意局限性,将大号和小号组合在一起时遇到的问题等。
如果您在浮点数的极限附近工作,则需要检验您的假设:例如,确保您没有处理太大或太小的数字。如果您期望指数小于Infinity
,请进行测试。如果希望退出循环,请添加保护条件,以确保一定次数的迭代后退出并返回错误。 测试您的代码!确保在极端情况下行为正确。
此外,在适当的地方使用大数字库。如果可能,请对算法进行重新设计,使其对计算机更友好。编写许多算法是为了让数学家在教科书中编写它们很优雅,但是对于处理器执行它们却是不切实际的。经常有一些版本具有相同的功能,但对计算机更友好。
我不想在生产代码中添加if语句或break语句;该代码应尽可能地具有高性能。)
不要害怕循环中有一个if语句。如果总是产生相同的结果-例如您的突破永无休止-分支预测器很快就会流行起来,并且分支几乎没有成本。这是一个带有分支的循环,您需要特别注意这一点。
答案 1 :(得分:-1)
如何避免在发布模式下弹出此类问题?
始终在循环中包含一个限制。即
if (i > 100_000) break;
您已经注意到,浮点运算在边缘处有些不可预测。与Release / Debug模式的链接不是很清楚,但是优化器可以像这样更改结果。