2013-07-16 53 views
3

我已经阅读了大量有关浮动错误和浮动近似等等的内容。
事情是:我从来没有读过现实世界问题的答案。而今天,我遇到了一个真实的世界问题。这真的很糟糕,我真的不知道如何逃脱。如何将浮点型与不连续函数结合起来?

看看这个例子:

[TestMethod] 
    public void TestMethod1() 
    { 
     float t1 = 8460.32F; 
     float t2 = 5990; 
     var x = t1 - t2; 
     var y = F(x); 

     Assert.AreEqual(x, y); 
    } 

    float F(float x) 
    { 
     if (x <= 2470.32F) { return x; } 
     else { return -x; } 
    } 

x应该是2470.32。但实际上,由于舍入误差,其值为2470.32031
大多数情况下,这不是问题。函数是连续的,并且一切都很好,结果有一点价值。
但是在这里,我们有一个不连续的函数,错误真的很大。该测试完全在不连续点上失败。

我该如何处理不连续函数的舍入误差?

+0

那么问题是什么?我可以想到四个潜在的问题:首先,常量8460.32将被转换为一个没有精确值的浮点数,因为该精确值不能表示为浮点数。其次,2470.32也是如此。第三,减去两个浮点数可能会导致另一个错误,第四,比较大小的代码可能不正确。四个问题中的哪一个,如果有的话,是你的实际问题? –

+0

这个问题的答案可能会也可能不会有所帮助:http://stackoverflow.com/questions/3420009/avoid-rounding-error-floating-specifically-c –

+1

@EricLippert从周围的浮动减去5990附近浮动8460总是精确的(“Sterbenz引理”),但是具有不同的常量,可能会有减法引起的误差。 –

回答

6

这里的关键问题是:

  • 功能在某些情况下,输出值产生较大的(显著)变化时,有输入值变化小。
  • 您正在向函数传递错误的输入值。

正如你写的,“由于舍入误差,[x的值]是2470.32031”。假设你可以编写任何你想要的代码 - 简单地描述要执行的功能,一组专家程序员将在几秒钟内提供完整的,无缺陷的源代码。你会告诉他们什么?

您提出的问题是“我将通过一个错误的值,2470.32031,到这个函数。我希望它知道正确的值是别的,并提供正确的值,我没有通过的结果,而不是我通过的不正确的值。“

一般来说,这个问题是不可能解决,因为无法区分2470.32031何时传递给函数,但2470.32是从2470.32031传递给函数并计划2470.32031时开始的。你不能指望电脑读你的思想。当你传递不正确的输入时,你不能指望正确的输出。

这告诉我们的是,功能F内部没有解决方案是可能的。因此,我们必须缩小,看看更大的问题。您必须检查传递给F的值是否可以得到改善(以更好的方式或以更高的精度或附加信息计算),或者问题的性质是否如此,以致当2470.32031通过时,始终有意使用2470.32,这样这个知识可以合并到F.

+0

我喜欢“一组专家程序员将在几秒钟内提供完整的,无缺陷的源代码”。 :) – tmyklebu

+0

坦克让你的手指重要。我通过调整调用F的函数的比较来解决这个问题。这不是一个很好的解决方案,但它可以工作。 –

+0

+1:非常简洁。 –

1

如果您需要8460.32数字与没有舍入错误的数字完全相同,您可以查看.NET Decimal类型,该类型明确创建以表示基数为10的小数,而不舍入错误。他们如何执行这种魔法超越了我。

现在,我意识到这可能是不切实际的,因为float大概来自某处并将其重构为Decimal类型可能太多了,但如果您需要它具有非常精确的不连续性函数依赖于这个值,你需要一个更精确的类型或者一些数学欺骗。也许有一种方法可以始终确保创建一个具有四舍五入错误的浮点数,使其始终小于实际的数值?我不确定这样的事情是否存在,但它也应该解决你的问题。

+1

魔法简单;小数点是96位无符号整数,符号位以及0到28之间的整数,指示小数点的位置。因此,1.234将是96位整数1234,符号位正数,小数位3(小数位是从右边开始计算)。 –

-1

我总是假设在比较浮点值时,由于舍入问题需要小的误差范围。在你的情况下,这很可能意味着在你的测试方法中选择不太严格的值 - 例如,定义一个非常小的误差常量并从x中减去该值。这是与此相关的SO question

编辑以更好地解决结论性问题:假设在不连续性正好是上输出的函数并不重要,所以在它的任一侧稍微测试一下。如果它确实很重要,那么真正关于你可以做的最好的事情就是允许该功能的两个输出中的任何一个。

1

您的应用程序中有三个数字,您通过将它们表示为浮点数接受了每个数字中的不精确性。

所以我认为你可以合理地声称,在纸上十进制表示看你的程序是正确

(oneNumber +/- some imprecision) - (another number +/- some imprecision) 
     is not quite bigger than another number +/- some imprecision 

工作看起来不对,但是这不是你已经实现了什么。数据的来源是什么? 8460.32究竟有多精确?如果是8460.31999应该发生什么? 8460.32001?原始值是否被称为这种精度?

最后,如果您想对更精确的模型进行建模,请使用不同的数据类型,如其他位置所示。

2

注意:这个答案基本上和埃里克
一样。它只是启发了测试的观点,因为测试是一种规范形式。

这里的问题是,testMethod1不测试F.
它相当测试将十进制数8460.32转换为浮点数和浮点数相减是不精确的。
但这是测试的目的吗?
所有你可以说的是,在某些糟糕的情况下(接近不连续),输入上的一个小错误会导致输出的大错误,所以测试可以表示这是一个预期的结果。

请注意函数F几乎是完美的,除了浮点值2470.32F本身。
实际上,浮点近似值会过小数(精确到1/3200)。
所以答案应该是:

Assert.AreEqual(F(2470.32F), -2470.32F); /* because 2470.32F exceed the decimal 2470.32 */ 

如果你想测试这种低层次的要求,你需要具有高(任意波形/无限)精度的库来进行测试。

如果你无法承受函数F上的这种不精确性,那么Float是不匹配的,你必须找到另一个具有增加,任意或无限精度的实现。
您需要指定您的需求,并且testMethod1应该比现在更好地明确此规范。

相关问题