【问题标题】:How to combine float representation with discontinous function?如何将浮点表示与不连续函数结合起来?
【发布时间】:2013-07-16 14:40:37
【问题描述】:

我已经阅读了大量有关浮动误差和浮动近似等所有内容。
问题是:我从未读过现实世界问题的答案。今天,我遇到了一个现实世界的问题。而且这真的很糟糕,我真的不知道如何逃脱。

看看这个例子:

    [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
大多数时候,这不是问题。功能是连续的,一切都很好,结果有点偏离。
但是在这里,我们有一个不连续的函数,而且误差非常非常大。测试恰好在不连续点上失败。

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

【问题讨论】:

  • 那么问题出在哪里?我可以想到四个潜在的问题:首先,常量 8460.32 将被转换为不具有该精确值的浮点数,因为该精确值不能表示为浮点数。其次,2470.32 也是如此。第三,两个浮点数的减法会导致另一个错误,第四,进行幅度比较的代码可能不正确。如果有的话,这四个问题中的哪一个是您的实际问题?
  • 这个问题的答案可能有用也可能没用:stackoverflow.com/questions/3420009/…
  • @EricLippert 从 8460 左右的浮点数中减去 5990 左右的浮点数始终是精确的(“Sterbenz 引理”),但是对于不同的常数,减法可能会导致错误。
  • 不要使用不连续函数?在阈值处取一些小区域,并在两个行为之间进行线性插值?这不会使浮点相等性测试通过,但会产生最小的错误。
  • 我认为对于不连续函数和实用、有界精度、数字表示的问题,没有任何万能的解决方案。您能否提供更多关于不连续函数的原因以及评估它的上下文的背景信息?

标签: c# .net floating-point floating-accuracy


【解决方案1】:

这里的关键问题是:

  • 在某些情况下,当输入值发生微小变化时,函数的输出值会发生较大(且显着)的变化。
  • 您向函数传递的输入值不正确。

正如您所写,“由于舍入误差,[x 的值] 为 2470.32031”。假设您可以编写任何您想要的代码——只需描述要执行的功能,专家程序员团队将在几秒钟内提供完整、无错误的源代码。你会告诉他们什么?

您提出的问题是,“我将向此函数传递一个错误的值 2470.32031。我想让它知道正确的值是别的东西,并为我没有通过的正确值提供结果,而不是我通过的不正确的值。”

一般来说,这个问题是不可能解决的,因为无法区分何时将 2470.32031 传递给函数但 2470.32 是预期的,与 2470.32031 何时传递给函数和 2470.32031 是预期的。你不能指望电脑能读懂你的想法。当您传递不正确的输入时,您不能期望正确的输出。

这告诉我们在函数 F 内不可能有解。因此,我们必须缩小范围,看看更大的问题。您必须检查传递给 F 的值是否可以改进(以更好的方式或更高的精度或补充信息计算)或者问题的性质是否是这样的这方面的知识可以融入到F中。

【讨论】:

  • 我喜欢“专家程序员团队将在几秒钟内提供完整、无错误的源代码。” :)
  • 感谢您将手指放在重要的事情上。我通过调整调用 F 的函数的组成来解决问题。这不是一个很好的解决方案,但它可以工作。
【解决方案2】:

注意:此答案与 Eric 的答案基本相同
它只是启发了测试的观点,因为测试是一种规范。

这里的问题是 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 应该比现在更好地明确这个规范。

【讨论】:

    【解决方案3】:

    如果您需要 8460.32 数字完全没有舍入错误,您可以查看 .NET Decimal 类型,该类型是明确创建的,用于表示以 10 为基数的小数,而不会出现舍入错误。他们是如何施展魔法的,我无法理解。

    现在,我意识到这对你来说可能是不切实际的,因为浮点数可能来自某个地方,将它重构为 Decimal 类型可能做的太多了,但如果你需要它对不连续函数具有如此高的精度依赖于该值,您要么需要更精确的类型,要么需要一些数学技巧。也许有某种方法可以始终确保创建具有舍入误差的浮点数,使其始终小于实际数字?我不确定这样的事情是否存在,但它也应该可以解决您的问题。

    【讨论】:

    • 魔法很简单;小数是 96 位无符号整数、符号位和 0 到 28 之间的整数,指示小数点的位置。所以 1.234 将是 96 位整数 1234,正符号位,小数位 3。(小数位从右边开始计算。)
    【解决方案4】:

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

    所以我认为你可以合理地声称你的程序运行正常

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

    当在纸上以十进制表示形式查看时,它看起来是错误的,但这不是您实现的。数据的来源是什么? 8460.32 的精确度如何?如果是 8460.31999 应该发生什么? 8460.32001?原始值是否已知如此精确?

    最后,如果您想更准确地建模,请使用不同的数据类型,如其他地方所建议的那样。

    【讨论】:

      【解决方案5】:

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

      编辑以更好地解决结论性问题:据推测,函数在不连续点上输出什么并不重要准确,因此只需在它的任一侧进行轻微测试。如果它确实很重要,那么你能做的最好的事情就是在那个时候允许函数的两个输出中的任何一个。

      【讨论】:

        猜你喜欢
        • 2023-03-30
        • 2015-10-06
        • 1970-01-01
        • 2021-09-28
        • 1970-01-01
        • 2021-08-06
        • 1970-01-01
        • 2020-06-13
        • 1970-01-01
        相关资源
        最近更新 更多