【问题标题】:Is double Multiplication Broken in .NET? [duplicate].NET 中的双乘法是否损坏? [复制]
【发布时间】:2010-11-28 02:10:43
【问题描述】:

如果我在 C# 中执行以下表达式:

double i = 10*0.69;

i 是:6.8999999999999995。为什么?

我知道像 1/3 这样的数字很难用二进制表示,因为它有无限的循环小数位,但 0.69 不是这种情况。而0.69可以很容易地用二进制表示,一个二进制数表示69,另一个表示小数位的位置。

我该如何解决这个问题?使用decimal 类型?

【问题讨论】:

  • 请考虑一下:在已经存在多年的框架中偶然发现这样的错误的可能性有多大?
  • 在几乎任何其他正确实现浮点运算的语言/框架中,您都会得到类似的结果。
  • 嘿嘿。这是不变的真理;每当您阅读带有“...用 abc 语言破坏的乘法...”的主题时,它将是关于浮点的 :)
  • Brian:我认为这不是一个好方法。我认为您可以合理地假设可能存在问题,但我也认为自己研究它是合理的。回到那天,我记得发现了 Java 错误;这并不意味着我很特别,这只是一个意外。研究是需要教授的。
  • 我喜欢如何将其存储在 BINARY 中的解释依赖于指定 DECIMAL 位置的位置

标签: c# .net math


【解决方案1】:

但是为什么框架不能解决这个问题,对我隐藏这个问题,给我正确的答案,0.69!!!

因为你告诉它使用二进制浮点,而解决方案是使用十进制浮点,所以你建议框架应该忽略你指定的类型并使用 decimal 代替,因为它不是直接在硬件中实现的,所以速度要慢得多。

更有效的解决方案是不输出表示的全部值,并明确指定输出所需的精度。如果您将输出格式化为两位小数,您将看到预期的结果。但是,如果这是一个金融应用程序,十进制正是你应该使用的 - 你见过超人三世(和办公空间),不是吗;)

请注意,这都是无限范围的有限近似,只是 decimaldouble 使用了一组不同的近似。 decimal 的优点是它产生的近似值与您自己执行计算时所产生的近似值相同。例如,如果您计算 1/3,那么当它“足够好”时,您最终会停止写 3。

【讨论】:

    【解决方案2】:

    要解决它(例如显示在屏幕上)试试这个:

    double i = (double) Decimal.Multiply(10, (Decimal) 0.69);
    

    似乎每个人都回答了您的第一个问题,但忽略了第二部分。

    【讨论】:

    • 想知道为什么这不是公认的答案。
    【解决方案3】:

    因为您误解了浮点运算以及数据的存储方式。

    事实上,在这种特殊情况下,您的代码实际上并没有在执行时执行任何算术运算 - 编译器 会完成它,然后在生成的可执行文件中保存一个常量。但是,它不能存储 6.9 的精确值,因为该值不能以浮点格式精确表示,就像 1/3 不能以有限十进制表示形式精确存储一样。

    看看this article对你有没有帮助。

    【讨论】:

    • 我可能会表现出我的幼稚,但是为什么框架不能解决这个问题,对我隐藏这个问题并给我正确的答案,0.69!!!
    • @Dan:因为你可能不是意思 0.69。您可能指的是由相同位模式表示的其他许多值中的任何一个。如果你想表示本质上是十进制的值,你应该使用 decimal 类型开始。请注意,在这里选择“输出”是解决如何将特定类型的实例转换为 text 格式的问题......这与决定首先使用哪种类型有些不同.
    • Dan:因为“double”是计算机用语,意思是“我宁愿做得快也不愿做得对。”
    • @Jon:也许你应该考虑将你的名字添加到文章中。通过这种方式,它获得了额外的可信度。并且也许还添加它的编写日期(以防二进制浮点实现将来发生变化:))。
    • 我可以在所有文章的底部添加我的名字。会考虑的。至于约会——我认为没有必要。
    【解决方案4】:

    为什么框架不能解决这个问题,对我隐藏这个问题并给我 正确答案,0.69!!!

    不要再表现得像个懒惰的经理,要接受计算机虽然酷炫酷炫,但也有局限性。在您的具体情况下,它不仅仅是“隐藏”问题,因为您明确告诉它不要这样做。语言(计算机)提供了您没有选择的格式的替代方案。你选择了双精度,它比十进制有一定的优势,也有一定的缺点。现在,知道了答案,你会因为缺点没有神奇地消失而感到不安。

    作为一名程序员,您有责任向经理隐藏这个缺点,并且有很多方法可以做到这一点。但是,C# 的制造者有责任使浮点正确工作,正确的浮点有时会导致错误的数学运算。

    所有其他数字存储方法也是如此,因为我们没有无限位。作为程序员,我们的工作是利用有限的资源来完成很酷的事情。他们把你送了 90% 的路,把火炬带回家。

    【讨论】:

    • 诚然,我对此并没有深入的了解。但我在想必须有一些解决方法,比如使用一些替代符号,它似乎是数字 69,它很容易以二进制形式存储,以及小数点的位置。
    • @Dan:本质上(我相信)这就是 Decimal 的作用。所以是的,如果你想要一个确切的答案,你应该使用它。
    • (对 Dan 的很多困惑)- 是的,Decimal 确实解决了这个特定问题。它增加了一些它自己的。如果不知道问题的细节,很难说哪个对你来说是“正确的”。您的问题的“正确”解决方案很可能是存储一个 Double 并为您的用户四舍五入。
    • @Dan:小数点以二进制形式存储。所以它出现在 1/2、1/4、1/8、1/16、1/32 等位置之后。
    • 阅读binary-system.base-conversion.ro/…,看看为什么电脑会显示给你!
    【解决方案5】:

    而 0.69 可以很容易地表示为 二进制,一个二进制数表示 69 和 另一个来表示位置 小数位。

    我认为这是一个常见的错误 - 您将浮点数视为以 10 为底(即十进制 - 因此我强调)。

    所以 - 你认为这个 double 有两个整数部分:69除以 100 来移动小数位 - 这可能也可以表示为:
    69 x 10 的 -2 次方

    然而,浮点数将“点的位置”存储为 base-2

    您的浮点数实际上存储为:
    68999999999999995 x 2 的某个大负数的幂

    一旦您习惯了,这不是什么大问题 - 大多数人都知道并期望 1/3 不能准确地表示为小数或百分比。只是base-2不能表达的分数不同而已。

    【讨论】:

    • 我们可能只需要说“重复的二进制位置”和“移动二进制点”就可以理解这个想法。
    • “68999999999999995 x 2 的某个大负数的幂”不是真的。尾数看起来不像 690(...)0;这是完全不同的东西。
    • @NayukiMinase 是的,我知道,我只是为了解释而试图简化它。真正的尾数看起来像一个不相关的数字并且是二进制的。这是人们以十进制表示的概念(例如 1/3 是 0.333 ...),但没有意识到适用于二进制中的不同数字(例如 1.1)
    【解决方案6】:

    出于同样的原因,十进制系统中的 1 / 3 输出为 0.3333333333333333333333333333333333333333333 而不是精确的分数,它是无限长的。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-28
    • 2013-12-05
    • 1970-01-01
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多