.NET C# float.Parse returning different result to double.Parse
一些更有经验的人能解释我今天发现的这个奇怪的错误吗?当我用我的C脚本加载表数据时,我得到了奇怪的数量。
事实证明,问题是类似功能的不同输出:
1 2 3 4 5 6
| 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,而不是double或float。
- @fildor@ron"amount"只是一个数量,不一定是一种货币。运营商没有指明货币。而且,要友善,不需要使用!。
- @费尔多,你应该解释一下为什么他不应该使用浮点类型
- @米基,你说得对,"数额"也可以是非货币的。已收回注释。旁注:我对"!"不是那么敏感。.我来自哪里(当用单数"!"不是!!!!!!!!")不要引起冒犯。
- Jonskeet对此有很好的解释,基本上double和float是二进制点类型(例如,double或float不能等于0.1),decimal是十进制(以10为基数)点类型。如果你在寻找点的准确性,你应该使用decimal!
- 浮点只有7位精度。docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
- 但精度如何影响将单个值解析为float或double?在@ivangarc&237;topete共享的主题中,有一个例子-但它显示了划分的不同。我得到了数学运算(如除法)中类型之间的区别。但是在我的场景中没有数学划分。为什么即使你不分任何东西也有区别?
当"1234567.15"转换为double时,结果是double中可表示的最接近的值,即1234567.149999999906867742538421484375。尽管您在问题中报告的值为1234567.15,但实际值为1234567.149999999906867742538421484375。当数值以有限的小数位数显示时,将显示"1234567.15"。
当"1234567.15"转换为float时,结果是float中可表示的最接近值,即1234567.125。尽管您报告的值是1234567.13,但实际值是1234567.125。当数值以有限的小数位数显示时,可能会显示"1234567.13"。
观察1234567超过1048576,即220。用于float的32位浮点格式使用24位作为有效位(数字的小数部分)。如果有效位的高位代表220,低位代表220?23=2?3=?。这就是为什么您看到"1234567.15"转换为四舍五入到最接近的八分之一的值。
- 这很有趣!很好的例子,我想我终于明白了,谢谢。所以当分析float时,我的"1234567.15"将其浮动,它得到"1234567"+最接近的1/8,所以它的"1234567.125"。为什么它会减少到2个小数?我没有具体说明……)
- @伊万博斯曼斯基:你没有告诉我们你是怎么得到"1234567.13"的;它只是作为一个评论出现在你的问题中。因此,我们不知道您是否使用某种格式化程序显示它,是否设置了某种格式/转换规范,或者什么。您必须显示完整的代码才能解释这一点。
- @米基德:对于埃多克斯1〔1〕,四舍五入到最近的?,如上所述。对于double,四舍五入到最接近的2^(20-52)=2^-32。最简单的方法是用double.Parse转换数字,然后在小数点后用32位或更多的十进制数字打印。(我不知道C是否能准确地打印这么多数字。微软并不总是提供良好的转换程序。我使用了苹果的开发工具来实现这一点,它确实可以准确地转换。我用了c:printf(%.99g
", 1234567.15);,如果有人想更详细地研究数学,扩展精度软件可能会有所帮助。
- 是的,我试着用数字做实验,并用不同的格式打印出来,这是绝对正确的——它的行为就像你描述的那样。多谢你的解释。抱歉,伙计们没能早点拿到,我只是想看看我猜的数字。
- 只是说"失去精确性"的一种花哨的方式,哈哈。+ 1
- 谢谢埃里克!精彩的回答。我不知道这是怎么回事:)我今天学到了一些新东西。+ 1
浮点数从不精确,它们是数字的表示。一个常用的例子是
1/3 + 1/3 = 2/3
…所以浮点数的答案,.33333 + .33333不是2/3rds,而是.66666。
长话短说,你拿的更精确的分数,不能转换成精确的二进制数字,总是会有一个舍入数。越精确,就越有可能出现舍入误差。
请记住,如果您使用多个不同的分数,您甚至可以有多个不同的舍入错误,这些错误会使数字意外地正确,甚至进一步偏离。
- 但一个double也代表一个浮点数。
- 双精度数是64位,浮点数是32位。更高的精度意味着不同的舍入。
- 但不确定欧普的问题是关于有理数的。
- 我不知道我为什么在这里被否决。@米基德可能是这样,但他正在把一个字符串解析为一个双精度数,并根据相同的字符串解析一个浮点数。关键是,在这些基于浮点数的类型中,有一个最低有效位会导致舍入错误。我有一种感觉,如果op乘以它们的结果乘以10的几个因子,那么它们的答案就会相等。
- 下面是微软关于IEE浮点格式如何与舍入一起工作的文章:docs.microsoft.com/en-us/cpp/build/reference/…
- @Obizues不是反对者,但问题在于EDOCX1(文化),而不是使用的类型。此外,乘法不能回答:如果是string ammount_in_string = 34028229999999999999999999999999999999.15,会发生什么?乘以10将成为一个OverflowException。
- 好的,我知道double和float的区别,从现在起我将使用decimal来避免这些问题。然而,我仍然不明白的是——为什么要进行四舍五入?它应该只解析一个值-不存在除法、乘法或任何其他数学运算…
你可以看到结果(这里也存在文化问题)
https://dotnetfidle.net/lnv1q7
1 2 3 4 5 6 7 8
| 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); |
结果(在不正确的区域性中分析!)
1 2 3
| 1234567,15
123456715
1.234567E+08 |
结果(在正确的区域性中分析!)
1 2 3
| 1234567.15
1234567.15
1234567 |
另一个用float证明精度损失的方法。
1 2 3 4
| float flt = 1F/3;
double dbl = 1D/3;
decimal dcm = 1M/3;
Console.WriteLine("float: {0} double: {1} decimal: {2}", flt, dbl, dcm); |
结果
1 2 3
| float: 0.3333333
double: 0.333333333333333
decimal: 0.3333333333333333333333333333 |
浮点数只能在某些精度损失不是非常有价值的情况下使用。这是因为浮点数是32位,而十进制数是128位。浮动主要用于像素坐标,精度的损失并不重要,因为消费者可以以任何方式将位置转换为更精确的坐标。
在.NET中,除非您不关心(某些)精度的损失,否则应该积极避免浮动。这可能永远不会是一天(除非你写游戏)
这就是银行业问题的根源所在,在每笔交易中损失了100美分/美分,看似看不见,但相当于大量的"失踪"资金。
使用小数
- 为什么投反对票?我用标准、简单的C代码演示了两次精度损失。再加上这里使用的一个例子,来自另一个有1K投票权的SO答案。我不喜欢被否决。没有人能学到任何东西。行为准则应强制对否决票发表意见。
- "……"应该在投票失败时强行发表评论吗?——我认为关于这一点,已经有了一个关于meta的讨论。有利弊。似乎缺点更大。
- dv可能是因为问题不在于位数,而在于float是十进制数的二进制表示,而decimal则不是。
- 是的,我提到的浮动与像素坐标(最常见)一起使用。赞成。。我以为我看到他在装货币价值表。
- 当你在.NET中感同身受的时候,永远不应该使用浮动(并且离开"除非你不在乎失去(某些)精确性"),我的大脑只会读到大胆的部分。也许您可以将其更改为仅在.NET中使用浮动,这可能(或将,取决于所需的精度)导致精度损失。
- 嗯,记住,你从来没有得到过"1234567.13",就像OP声称的那样。
- @伊万加克可能在山顶。无论你想不想,floats都会造成进位的损失,而且它也不需要任何复杂的东西。
- 是的,我也从来没有去过.13,但这可能是一个文化背景问题。检查您的文化设置
- 另外,如果op使用,作为十进制分隔符来获取数据,那么您可以包含一个代码片段来处理它(比如decimal.Parse(ammount_in_string.Replace(",","."))或与decimal.parse(string,?编号样式,?iformatProvider)?)
- 1234567.13如何由文化问题引起?如果区域设置不匹配,解析器应该生成一个异常或将逗号作为千位分隔符。这两者都不会产生1234567.13,也不会接受输入并将其转换为无效字符。接受并将整个字符串转换为32位浮点,逗号或句点作为小数点,将生成1234567.125,显示为1234567.13。
- 谢谢你,我觉得它很有用。然而,我真的得到了.13,我不认为文化解释了.15为什么改为.13。它可以解释逗号和句点分隔符等的任何问题,但是像这样更改值?
- 您在答案中发布的链接处的代码为float.Parse生成1234567.125。它只显示为"1234567",因为显然默认格式限制了显示的数字。打印更多的数字会显示"1234567.13"。因此,op的结果不是由于区域设置/区域性不匹配,而是由于浮点精度。
- 好的,说得通。对。区域性位与格式化输出相关,而不是它本身的值。虽然…在某些处理器上,舍入可能会因endianness、软浮点或硬浮点及其实现而不同。
- @ppumkin:ieee 754四舍五入是数学上定义的。结尾不起作用。IEEE754四舍五入是相同的,不管它是在硬件还是软件中实现。