【问题标题】:Why does Visual Studio 2008 tell me .9 - .8999999999999995 = 0.00000000000000055511151231257827?为什么 Visual Studio 2008 告诉我 .9 - .8999999999999995 = 0.00000000000000055511151231257827?
【发布时间】:2010-12-12 08:47:48
【问题描述】:

当我在 Visual Studio 2008 即时窗口中键入此内容时:

? .9 - .8999999999999995

它给了我这个答案:

0.00000000000000055511151231257827

文档说 double 具有 15-16 位精度,但它给我的结果是 32 位精度。这么多额外的精度从何而来?

【问题讨论】:

  • 我想知道我们在 SO 上有多少与浮点精度问题相关的问题。
  • @Mehrdad:太多了。这是一件如此简单的事情:以 10 为基数!= 以 2 为基数。然而,每个人都觉得有必要把它当作有史以来最神奇的事情来对待!
  • 人们,请阅读,这就是为什么答案使用 32 位数字。不是不准确。
  • Henk,请理解,浮点数不是这样工作的。
  • @Mehrdad:我写了一个脚本来统计,有201.00000001918371个这样的问题。不知道我是如何计算分数问题的,也许我会在我的源代码中发布一个问题来寻求解释。

标签: visual-studio precision immediate-window


【解决方案1】:

前导零不重要/精度的一部分(就浮点数而言——从数学上讲,它们重要的)。前导零是浮点数内部表示的指数部分。

55511151231257827 部分(即 significandmantissa)有 17 位十进制数字,足够接近 15-16 位数字。

@Lars D:你认为正确的,只有在问题的上下文中是正确的.9 - .8999999999999995 的浮点数为 0.625,指数为 -50。取 0.625 * 2-50 结果为 5.5511151231257827e-16。现在,在原始问题的上下文之外,我们有一个 17 位有效数字的数字,确实恰好是我们最好的二进制近似值 0.0000000000000005。但是,就浮点数的表示而言,那些前导零仍然不重要。

【讨论】:

  • 我对这个答案投了反对票,因为我认为你的答案不正确。实际减法运算中的 15-16 位精度给出 0.0000000000000005,其余数字为随机舍入误差。因此,在55511151231257827部分中,只有1位正确,其余为舍入错误,因前导零而被提升为尾数。基本上,这 32 位数字是减法后的 16 位有效数字,再加上尾数中的 16 位噪声。只有在存储结果后,零才会变得无关紧要。
  • 对 - 如果我再次学习化学,前导零 是有效数字。但是,计算机不知道零是否有意义,这就是为什么答案中表示大约有 16 个非零数字。
【解决方案2】:

我认为这是因为在二进制系统中,5 是周期性的,因为它不能被 2 整除。然后 Mark Rushakoff 所说的适用。

【讨论】:

    【解决方案3】:

    ? .9 - .8999999999999995

    这个减法过程,有 15-16 个有效数字,给出

    0.0000000000000005

    其余数字只是舍入误差。但是,由于计算机总是在第一个非零数字之后存储 15-16 位有效数字,因此会显示舍入误差,并且您会得到大量由舍入误差产生的尾随随机数字。因此,减法运算的结果有 16 位有效数字加上存储结果的 16 位,即 32 位。

    【讨论】:

      【解决方案4】:

      您应该阅读:What Every Computer Scientist Should Know About Floating-Point Arithmetic

      基本上它归结为以有限精度存储的浮点数。您必须与一些 delta 进行比较。

      if(.9 - .8999999999999995 <= 0.0001)
        //close enough to be equal
      

      【讨论】:

      • +1:很遗憾,SO 无法自动回答每个浮点问题,因为它是至少 90% 浮点问题的正确答案。
      • 这并没有解决他关于精度的问题。
      • -1,不回答问题。对于 double,q 大约是 32 位(不是 16 位)。
      • +1 位数并不是真正相关的部分,而是此答案所针对的存储方法。
      【解决方案5】:

      “浮点”的“浮动”部分意味着您得到的结果更接近 5.5511151231257827 * 10^(-16)。这并不是它的确切表示方式,因为当然这一切都是在引擎盖下以二进制形式完成的,但关键是,数字由有效数字表示,加上一个表示基数(小数点)移动多远的数字。与往常一样,维基百科可以为您提供更多详细信息:

      (第二个链接更具体地关注您的具体情况。)

      【讨论】:

        【解决方案6】:

        答案中只有 15-16 位数字。所有这些前导零都不算数。这个数字实际上更像是 5.5511151231257827 × 10-16。尾数部分有 15-16 位数字。指数 (-16) 用于将小数点移动 16 位,但不会更改总数中的位数。

        编辑

        得到一些 cmets 后,我现在很好奇到底发生了什么。我将有问题的号码插入此IEEE-754 Converter。冒昧地将最后的“27”四舍五入为“30”,但我认为这不会改变结果。

        转换器将数字分解为三个二进制部分:

        符号:0(正)
        指数:-51
        有效数字:1.0100000000000000000000000000000000000000000000000000(二进制为1.2510

        所以这个数字是 1.012 × 2-51,或 1.2510 × 2-51。由于只存储了三个有效的二进制数字,这表明 Lars 可能正在做某事。它们不能是“随机噪声”,因为每次转换数字时它们都是相同的。

        数据表明唯一存储的数字是“5”。前导零来自指数,其余看似随机的数字来自计算 2-51

        【讨论】:

        • 所有其他数字存储在哪里?
        • 不是,它是二进制点数的十进制表示。包括它的舍入误差。
        • 错误:“前导零不算数”不正确。答案中的 15-16 个重要数字是 0.0000000000000005。之后的数字不正确,并且仅因为舍入误差因前导零而被提升为尾数而存在。正如 Barry 在他的编辑中自己写的那样,结果存储为 1.25*2^(-51)=5.551*10^(-16),这是一个具有 1 个小数位的计算精度的数字,但具有 15-16存储精度位数。
        • Convert.ToSingle("9111111.4999999990")convert.ToSingle("9111111.4999999991") 出现了更有趣的行为。这两个值都更接近 9111111 而不是 9111112,但后者向上取整。
        猜你喜欢
        • 1970-01-01
        • 2015-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-13
        • 2017-03-25
        • 2011-07-13
        相关资源
        最近更新 更多