RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1183173
Accepted
Aimon Z.
Aimon Z.
Asked:2020-09-27 22:42:51 +0000 UTC2020-09-27 22:42:51 +0000 UTC 2020-09-27 22:42:51 +0000 UTC

浮点精度问题

  • 772

有这个代码:

Debug.Log((p > 10));

变量 p 还包含数字 10。

我正在运行给定代码的 100 次迭代。

结果是:90 条虚假消息和 10 条真实消息

所有消息都必须为 False。

为了防止这种情况发生,Mathf.Approximately 用于 == 运算符。> 和 < 运算符呢?我试过这样,但没有帮助:

Debug.Log((p > 10 + Mathf.Epsilon));

如何解决问题?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    KoVadim
    2020-09-28T16:40:17Z2020-09-28T16:40:17Z

    花了很长时间,但我希望它很有趣。然后我已经厌倦了用两行写答案:)

    老故事

    我在大学读书的时候,遇到了一个很大的计算问题,有两种计算方式。最后,这两个答案必须收敛。老师允许有20%的差异。但问题是一个计算分支大约只有半百个乘法。还有三角函数和对数。如果这个人明白他在做什么,那么计算本身大约需要一个小时。

    并且计算分析没有显示错误。如果邻居做了同样的计算,那么他得到了他自己的一对数字。当我有 3 对数字时,我意识到这行不通。

    数学家睁开眼睛

    但后来原因变成了——在计算器上进行计算时,我几乎总是自动四舍五入到小数点后 2-3 位(懒得重写 10-12 位)。数学老师看着这个说,很容易得到一倍半到两倍的点差(其实是观察到的)。

    因此,我用 Delfi 武装自己并编写了自己的计算。最后,它以百分之一的准确度结合在一起。对比我的手工计算,发现误差逐渐累积。但是接受作业的老师有点震惊(她没有看到数字会收敛这么多,并试图用计算器找到我“适合”的确切位置)。

    物理学家也知道

    因此,当计算的结果写在物理学中时(例如,在实验室工作或博士论文中),没有指明错误,它们“没有任何物理意义”。

    然后我用每一步的误差计算做了另一个计算,一切都在一起了,但更多的是下面。

    现在更接近这个问题了。

    让我们看看获得数字 10 的这些选项(是的,这段代码是有利的,但它不会改变任何东西)

    #include <string>
    #include <iostream>
    #include <cmath>
    #include <iomanip>
    
    void print(float f)
    {
        std::cout
        // точность вывода по умолчанию
        << std::setprecision(6) << f << " = "
        // максимальная точность вывода
        << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
        << f << '\n';
    }
    
    int main()
    {
        float f1 = 10.0;
        print(f1);
        //
        float f2 = 1.0 / 3.0 * 30;
        print(f2);
        //
        float f3 = 0;
        for (int i = 0; i < 100; i++) { f3+=0.1;}
        print(f3);
        //
        float f4 = 0;
        for (int i = 0; i < 1000; i++) { f4+=0.01;}
        print(f4);
         //
         float f5 = exp(log(2)+log(5));
         print(f5);
    }
    

    结论

    10 = 10 10 = 10 10 = 10.00000190734863281 10.0001 = 10.00013351440429688 10 = 10

    尽管所有这些方法都给出了 10,但它们都是有点“不同的 10”。在第二和第五种情况下,编译器优化并且有“诚实的10.0”(是的,它们是编译器。有时它们可​​以创造奇迹并隐藏用户错误。

    现在您需要了解两个实数的 == 运算符仅进行按位比较(如果类型不匹配,则首先需要“调整”它们,这些是附加错误)。

    还有,马上就清楚,在第四种情况下,输出的时候,看起来好像有10个,其​​实不然。

    怎么会这样?

    现在让我们走得更近。在您的代码中,您似乎正在执行一些应该给出相同结果的计算。但是你得到它们有点不同(例如,这些可以是使用蒙特卡洛方法计算图形的面积)。

    游戏也是最新的

    游戏中也有类似的情况。假设我们有一个步行者,而主角只能以一定大小的步数移动。自然地,我们将步长添加/减去其当前坐标。而在房间里走来走去,回到起点,我们可以发现,我们已经发生了一点变化。这有时是“非常出乎意料的”。在同一个射击游戏中,一颗子弹可以在“附近”飞行,尽管我们的目标非常准确。

    我们正在寻找解决方案

    该怎么办?有必要考虑结果中的错误。有时他们建议使用同一个EPS,但这不是正确的方法。我们需要比较两个结果,而不是作为点结果,而是作为两个段。

    简单复杂的例子

    让我们看一下这样的比较 10±3 和 9±2。如果我们将它们翻译成“段”,它将是 [7;13] 和 [7;11]。最有可能的是,大多数人会考虑第一个而不是第二个。但是,考虑到误差,它们可以相等。他们可能不是。因此,正确答案是:10±3 ≊ 9±2(这等于或几乎等于)

    新版本 10±3 和 5±2 -> [7;13] 和 [3;7]。尽管乍一看似乎第二个数字肯定更少,但事实并非如此。在某些情况下,它们可以等于 7。是的,这种可能性非常低,但仍然存在。因此,这里正确的符号是 10±3 ≥ 5±2,不管它多么奇怪。

    但是很多人都忘记了。然后得到了奇怪的结果……然后突然,“错误有错误吗?”

    怎样成为?

    考虑到这一切,如何比较?在这里,您总是需要查看目标任务。例如,如果这是地图上一个人的坐标,那么最好将坐标四舍五入到步重数,甚至将它们存储为整数。如果这些是船舶坐标,那么将船舶尺寸加倍将是准确度的一个很好的衡量标准。如果这是原子弹的计算,那么看起来他们在论坛上犯了一个错误。

    做出决定

    让我们继续看代码。我会做这个比较更多

    abs(a-b) > eps && a>b
    

    什么可以改写以及如何改写

    a > b + eps
    

    其中 eps 要么通过“戳”从合适的参考书中选择,要么只是两个变量的测量误差之和。

    这已经是对这个问题的正式回答

    让我们看看你的比较方式有什么问题

    Debug.Log((p > 10 + Mathf.Epsilon));
    

    他看起来像我的。但是如果你看看Mathf.Epsilon是什么,就会发现

    表示大于零的最小正双值。这个字段是一个常数。

    假设它是一个“原子”(即不可分割的,最小的)正数。不再可能将其分成两半。稍微想一想,很明显和他的比较有点没有意义——这个数字太小了

    public const double Epsilon = 4.94065645841247E-324;
    

    无论如何,上面的链接都说了同样的话。

    在创建确定两个浮点数是否相等的自定义算法时,我们不建议您将算法基于 Epsilon 常数的值,以实现两个值的可接受的绝对差值被视为相等。

    (这是某种机器翻译,但似乎可以理解)。

    附言

    我会把这个玩具放在这里https://evanw.github.io/float-toy/ - 它有助于理解里面的一切是如何旋转的。

    • 5

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5