-1

我正在尝试将浮点数四舍五入到小数点后 6 位。从 double 转换为 float 似乎几乎可以做到这一点,但是在此过程中我注意到了一些奇怪之处。无论我做什么,如果我稍后转换回精度值,我似乎最终会得到虚构的额外十进制值。我无法避免转换回双精度 - 这在我无法更改的应用程序的一部分中,所以我只是想了解为什么我的代码会产生原本不存在的额外小数位。

例如; 我从以下值开始并将其转换为双精度值:

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() 与调试器显示的相比,通常输出不同的小数位数?

4

5 回答 5

3

您将不得不使用,Math.Round功能。

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

您可以参考链接。

或者这样做:

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

请参阅链接。

于 2013-05-30T11:58:16.333 回答
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.

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

现在,当没有中间浮点转换时,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().


进一步阅读: Oracle 的 IEEE 算术。不过技术含量很高。

于 2013-06-01T06:52:10.520 回答
-1

默认情况下,文字 50.8467178 是双精度数,因此在分配给 foo 时会隐式转换为浮点数。而且由于浮动的精度较小,因此它是圆形的并且不是那么精确。分配给 foo 时应该使用 50.8467178f 因为它是一个浮点文字。在第二个示例中,您将双精度文字转换为双精度,因此没有任何更改,它按原样打印。

于 2013-05-30T12:03:30.423 回答
-1

这样做:

Convert.ToDouble(50.8467178).ToString()

和这样做是一样的:

(50.8467178).ToString();

由于数字的隐式类型是double,因此转换什么也不做。

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

我建议使用decimal, 直接强制转换而Equals不是==.

于 2013-05-30T12:03:33.000 回答
-1

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

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

result = Math.Round(result, 2);

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

C# 中的Afloat是数字的 32 位浮点表示,而 adouble是 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
于 2013-05-30T12:04:25.187 回答