C#中的舍入数字产生了太多小数位

时间:2013-05-30 11:56:21

标签: c# math floating-point rounding rounding-error

我正在尝试将 float 舍入到小数点后6位。从double转换为float似乎就是这样做的,但是在这个过程中我注意到了一些奇怪之处。 无论我做什么,如果我稍后转换回 double ,我似乎最终会得到虚构的额外小数值。我无法避免转换回加倍 - 这部分应用程序我无法改变,所以我只是想了解为什么我的代码产生了从未存在的额外小数位。

EG; 我从以下值开始并将其转换为double:

float foo = 50.8467178;
double bar = Convert.ToDouble32(foo); 
// Sorry line above originally said ToInt32 which was a typo :(

..然后调试器中的“bar”将是: 50.846717834472656

然而

Convert.ToDouble(50.8467178).ToString()

...产生: 50.8467178 (在调试器中)。即它没有额外的值。

为什么最后会出现额外的数字? 我怎么能阻止这个? 最后:为什么在上面的任何一个上调用ToString(),与调试器显示的相比,通常会输出不同的小数位数?

5 个答案:

答案 0 :(得分:3)

您必须使用Math.Round功能。

Math.Round(Convert.ToDouble(50.8467178)), 2);

您可以从THIS链接中获取参考。

或者这样做:

String.Format("{0:0.00}", Convert.ToDouble(50.8467178).ToString()); //two places

请参阅THIS链接。

答案 1 :(得分:0)

在这个例子中实际上发生了一些微妙的不同事情:

  

我试图将一个浮点数舍入到小数点后6位。从double转换为float似乎就是这样做的

如果您需要进行舍入,请使用Round方法。转换可能导致精度损失,浮点类型开始时无法无限精确。

  

为什么最后会出现额外的数字?

根据IEEE 754标准,内部浮点数和双精度数以二进制表示。您的数字,50.8467178不能用二进制精确表示,很像1/3不能用十进制精确表示(不重复数字)。根据标准,作为浮点数的数字存储为0x424B6304。这精确到24位或6-9位十进制数字。现在,根据我们得到的标准重新解释这个数字:

0x424B6304
= 1.58895993232727 * 2^5
= 50.84671783447264

显示器,无论是调试器还是调用ToString()方法,都足够聪明,只知道前6-9位数字很重要(这符合IEEE标准)。取最多9位数,浮点数将显示为50.8467178

但是,当这个数字转换为double时,它的二进制值不会改变。它的内部位模式确实如此,但二进制数仍然被解释为1.58895993232727 * 2^5。问题是双精度精确到53位,或15-17十进制数。所以现在显示时,显示的是15-17位,而不是原来的6-9位。所以,当它转换为double时,并不是有额外的数字,数字已经存在

现在,当没有中间浮点转换时,可以使用double更精确地表示50.8467178。它存储为0x40496C613FB5F875。现在我还没有对那个进行过数学计算,但是通过上面的过程,我们得到像50.84671780000000000000000000023155这样的东西。仅考虑前15-17位数,结果显示在显示中的50.8467178(省略了显着的0)。

  

我该怎么办?

不要通过强制转换为浮点数,它们只有6-9个准确数字。十进制类型通常适用于需要十进制精度的情况,但由于您无法更改代码的这一部分,因此使用Round方法应该足够精确适合您的应用程序(只要您保持在1,000,000,000以下)

  

为什么在上面的任何一个上调用ToString(),与调试器显示的相比,经常输出不同的小数位数?

我一直在绕过这个问题,试图保持简单和顺序。我一直给出十进制数字的精度范围:6-9为浮点数,15-17为双精度数。以双打为例,默认情况下ToString()方法返回一个15位十进制数的字符串。但是,您可以通过调用ToString("G17") (doc)强制它返回17位小数,但不能保证这两位数是重要的。我怀疑调试器会调用此版本进行显示,这就是它与ToString()不同的原因。


进一步阅读: IEEE Arithmetic, by Oracle。虽然这很技术性。

答案 2 :(得分:-1)

默认情况下,文字50.8467178是双精度数,因此在赋予foo时隐式转换为浮点数。并且因为浮子具有较小的精度,所以它是圆形的而不是那么精确。当你指向foo时你应该使用50.8467178f,因为它是一个浮点文字。在第二个示例中,您将双字面值转换为double,因此没有任何更改,并按原样打印。

答案 3 :(得分:-1)

这样做:

Convert.ToDouble(50.8467178).ToString()

与此相同:

(50.8467178).ToString();

由于数字的隐式类型为double,因此转换不执行任何操作。

但是当您将数字作为float开始时,转换会生效,并且已知double多次混淆(这种情况,以及比较值时等) ...)。

我建议使用decimal,直接演员和Equals代替==

答案 4 :(得分:-1)

出现额外小数位的原因可能是因为从double转换为float时有轻微的精度损失(浮点转换永远不会完美)。

如果您需要对数字进行舍入,那么在完成转换后,您可以执行以下操作:

result = Math.Round(result, 2);

编辑:鉴于Eric的评论如下,这里有一些额外的信息:

C#中的float是数字的32位浮点表示,而double是64位。并非每个数字都可以通过浮点数完美表示。当您从32位浮点数转换为64位双精度数据时,您有32个额外位来填充信息。这些额外信息最终会成为您看到的额外数字:

float foo = 50.8467178f;
double bar = System.Convert.ToDouble(foo);
System.Console.WriteLine(bar); // prints 50.8467178344727 on my machine

由于原始数字在浮点数下不能很好地表示,因此转换过程的输出结果会略有不同。

如果您需要保持此数字的精确度,则需要使用decimal值:

decimal foo = 50.8467178M;
double bar = System.Convert.ToDouble(foo);
System.Console.WriteLine(bar); // prints 50.8467178 on my machine

如果您无法使用decimal(例如float是第三方库的输出或其他内容),那么舍入方法是下一个最佳选择:

float foo = 50.8467178f;
double bar = System.Convert.ToDouble(foo);
bar = System.Math.Round(bar, 7);
System.Console.WriteLine(bar); // prints 50.8467178 on my machine