【问题标题】:.NET C# float.Parse returning different result to double.Parse [duplicate].NET C# float.Parse 将不同的结果返回给 double.Parse [重复]
【发布时间】:2019-01-29 23:46:30
【问题描述】:

能否请一些更有经验的人解释一下我今天发现的这个奇怪的错误? 当我用我的 C# 脚本加载表数据时,我得到了奇怪的数量。

事实证明,问题在于类似函数的输出不同:

string amount_in_string = "1234567,15";
double amount_in_double = double.Parse(amount_in_string);
float amount_in_float = float.Parse(amount_in_string);

//amount_in_double = 1234567.15;
//amount_in_float = 1234567.13;

当 float 和 double 是相似类型(浮点)时,为什么我会得到如此不同的结果。像这样少量的精度可以产生影响吗?

【问题讨论】:

  • 您应该为此使用decimal,而不是doublefloat
  • @Fildor @ron "amount" 只是一个数量,不一定是货币。 OP 没有指明货币。还有,乖一点,不用!
  • @Fildor 你应该解释一下为什么他不应该使用浮点类型
  • @MickyD 你是对的,“金额”也可以是非货币的。评论被撤回。旁注:我对“!”不是那么敏感。我来自哪里他们(当使用单数“!”,而不是“!!!!!!”)不会引起冒犯。
  • JonSkeet has a very good explanation on this subject,基本上 doublefloat 是二进制点类型(例如,doublefloat 永远不能等于 0.1)和 decimal 是十进制(以 10 为底)点类型。如果您正在寻找点精度,您应该改用decimal

标签: c# .net types floating-point precision


【解决方案1】:

当“1234567.15”转换为double时,结果是double中可表示的最接近的值,即1234567.1499999999068677425384521484375。尽管您在问题中报告该值为 1234567.15,但实际值为 1234567.1499999999068677425384521484375。当数值以有限的小数位数显示时,将显示“1234567.15”。

当“1234567.15”转换为float时,结果是float中可表示的closet值,即1234567.125。虽然您报告的值为 1234567.13,但实际值为 1234567.125。当值显示的小数位数有限时,可能会显示“1234567.13”。


观察到 1234567 超过了 1,048,576,即 220。用于float 的 32 位浮点格式使用 24 位作为有效数(数字的小数部分)。如果有效数字的高位表示220,则低位表示220−23 = 2-3 = ⅛。这就是为什么您会看到“1234567.15”转换为四舍五入到最接近八分之一的值。

【讨论】:

  • 这很有趣!很好的例子,我想我终于明白了,谢谢。因此,当将我的“1234567.15”解析为浮动时,它得到“1234567”+最接近的1/8,因此它的“1234567.125”。为什么它会减少到小数点后两位?我没有指定..:)
  • @IvanBosmansky:你没有告诉我们你是如何得到“1234567.13”的;它仅作为评论出现在您的问题中。所以我们不知道你是否用一些格式化程序显示它,如果你设置了一些格式/转换规范,或者什么。您必须显示完整的代码才能进行解释。
  • @MickyD:对于float,四舍五入到最接近的⅛,如上所述。对于double,四舍五入到最接近的 2^(20-52) = 2^-32。进行此四舍五入的最简单方法是将数字转换为 double.Parse,然后在小数点后使用 32 个或更多小数位进行打印。 (不知道C#能不能准确地打印出这么多数字。微软并不总是提供好的转换例程。我用苹果的开发者工具来做这个,转换准确。我用C:printf(%.99g\n", 1234567.15);。)如果有人想看在更详细的数学上,扩展精度软件可以提供帮助。
  • 是的,我尝试使用这些数字进行试验,并以不同的格式打印出来,这绝对是正确的——它的行为就像你描述的那样。非常感谢您的精彩解释。抱歉大家没有早点知道,我只是想看看我猜的数字
  • 只是一种说“精度损失”的奇特方式哈哈。 +1
【解决方案2】:

浮点数从来都不是精确的,它们是数字的表示。一个常用的例子是想想

1/3 + 1/3 = 2/3

...所以浮点数的答案 .33333 + .33333 不是精确的 2/3,而是 .66666

长话短说,无法转换为精确二进制的更精确分数将始终具有舍入数。越精确,就越有可能出现舍入误差。

请记住,如果您使用多个不同的分数,您甚至可能会出现多个不同的舍入错误,这些错误会导致数字意外正确,甚至更远。

【讨论】:

  • 双精度是 64 位,浮点是 32 位。更高的精度意味着不同的舍入。
  • 不确定 OP 的问题是关于有理数的
  • 我不知道为什么我在这里被否决。 @MickyD 可能是这样,但他正在将一个字符串解析为双精度浮点数和基于同一字符串的浮点数。关键是在任何这些基于浮点数的数字类型中都有一个最低有效位会导致舍入错误。我有一种感觉,如果 Op 将他们的结果乘以 10 的几个因数,那么他们的答案将是相等的。
  • 这是微软的文章,解释了 IEEE 浮点格式如何与舍入一起使用:docs.microsoft.com/en-us/cpp/build/reference/…
  • @obizues 不是反对者,但问题在于,(文化),不完全是使用的类型。此外,乘法不能成为答案:string ammount_in_string = 34028229999999999999999999999999999999.15 的情况会发生什么?乘以 10 将变为 OverflowException
【解决方案3】:

你可以看到结果的小提琴(这里也有文化问题)

https://dotnetfiddle.net/Lnv1q7

    string amount_in_string = "1234567,15"; // NOTE THE COMMA in original
    string amount_in_string = "1234567.15"; //my correct culture
    double amount_in_double = double.Parse(amount_in_string);
    float amount_in_float = float.Parse(amount_in_string);

    Console.WriteLine(amount_in_string);
    Console.WriteLine(amount_in_double);
    Console.WriteLine(amount_in_float);

结果(在不正确的文化中解析!)

1234567,15
123456715
1.234567E+08

结果(以正确的文化解析!)

1234567.15
1234567.15
1234567

另一个用float 证明精度损失

float flt = 1F/3;
double dbl = 1D/3;
decimal dcm = 1M/3;
Console.WriteLine("float: {0} double: {1} decimal: {2}", flt, dbl, dcm);

结果

float: 0.3333333  
double: 0.333333333333333  
decimal: 0.3333333333333333333333333333

浮点数只应在精度损失不是非常有价值的情况下使用。这是因为浮点数是 32 位,十进制是 128 位 浮点数主要用于像素坐标,精度损失无关紧要,因为消费者会以任何方式将位置转换为更精确的坐标。

“应该”积极避免在 .NET 中浮动,除非您不关心(某些)精度的损失。现在可能永远不会有这样的日子(除非你写游戏)

这就是银行问题的由来,当交易中有 100 美分/美分丢失时,每笔交易看似不可见,但相当于大量“丢​​失”的钱。

使用小数

【讨论】:

  • 为什么投反对票?我使用标准、简单的 C# 代码两次证明了精度损失。加上这里使用的另一个 SO 答案的示例,获得了 1k 的赞成票。我讨厌通过downvotes驱动..没有人学到任何东西。行为准则应强制 cmets 投反对票
  • 当你同情 在 .NET 中永远不应该使用浮点数(并留下“除非你不关心(某些)精度的损失。”)我的想法而已读取粗体部分。也许您可以将其更改为仅 在 .NET 中使用浮点数可能(或将会,取决于所需的精度)导致精度损失。
  • hm,请注意,您从来没有像 OP 声称的那样得到“1234567.13”。
  • 1234567.13 怎么可能是文化问题引起的?如果语言环境不匹配,解析器应该生成异常或将逗号作为千位分隔符。这些都不会产生 1234567.13,也不会接受输入并将其转换为无效字符。接受整个字符串并将其转换为 32 位浮点数,以逗号或句点作为小数点,将生成 1234567.125,可以显示为 1234567.13。
  • 您在答案中发布的链接中的代码为 float.Parse 生成 1234567.125。它仅显示为“1234567”,因为显然默认格式限制了显示的数字。打印更多数字将显示“1234567.13”。所以 OP 的结果不是是由于语言环境/文化不匹配,而是由于浮点精度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多