可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Description
This is not a real world example! Please don't suggest using decimal
or something else.
I am only asking this because I really want to know why this happens.
I recently saw the awesome Tekpub Webcast Mastering C# 4.0 with Jon Skeet again.
On episode 7 - Decimals and Floating Points it is going really weird and even our
Chuck Norris of Programming (aka Jon Skeet) does not have a real answer to my question.
Only a might be.
Question: Why did MyTestMethod()
fail and MyTestMethod2()
pass?
Example 1
[Test]
public void MyTestMethod()
{
double d = 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
Console.WriteLine("d = " + d);
Assert.AreEqual(d, 1.0d);
}
This results in
d = 1
Expected: 0.99999999999999989d
But was: 1.0d
Example 2
[Test]
public void MyTestMethod2()
{
double d = 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
d += 0.1d;
Console.WriteLine("d = " + d);
Assert.AreEqual(d, 0.5d);
}
This results in success
d = 0,5
But why ?
Update
Why doesn't Assert.AreEqual()
cover that?
回答1:
Okay, I haven't checked what Assert.AreEqual
does... but I suspect that by default it's not applying any tolerance. I wouldn't expect it to behind my back. So let's look for another explanation...
You're basically seeing a coincidence - the answer after four additions happens to be the exact value, probably because the lowest bit gets lost somewhere when the magnitude changes - I haven't looked at the bit patterns involved, but if you use DoubleConverter.ToExactString
(my own code) you can see exactly what the value is at any point:
using System;
public class Test
{
public static void Main()
{
double d = 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
d += 0.1d;
Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
}
}
Results (on my box):
d = 0.1000000000000000055511151231257827021181583404541015625
d = 0.200000000000000011102230246251565404236316680908203125
d = 0.3000000000000000444089209850062616169452667236328125
d = 0.40000000000000002220446049250313080847263336181640625
d = 0.5
Now if you start with a different number, it doesn't work itself out in the same way:
(Starting with d=10.1)
d = 10.0999999999999996447286321199499070644378662109375
d = 10.199999999999999289457264239899814128875732421875
d = 10.2999999999999989341858963598497211933135986328125
d = 10.39999999999999857891452847979962825775146484375
d = 10.4999999999999982236431605997495353221893310546875
So basically you happened to get lucky or unlucky with your test - the errors cancelled themselves out.
回答2:
Assert.AreEqual()
does cover that; you have to use the overload with a third delta
argument:
Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);
回答3:
Because Doubles, like all floating point numbers, are approximations, not absolute values binary (base-2) representations, which may not be able to perfectly represent base-10 fractions (the same way that base-10 cannot represent 1/3 perfectly). So the fact that the second one happens to round to the correct value when you perform equality comparison (and the fact that the first one doesn't) is just luck, and not a bug in the framework or anything else.
Also, read this: Casting a result to float in method returning float changes result
Assert.Equals does not cover this case because the principle of least astonishment states that since every other built-in numeric value type in .NET defines .Equals() to perform an equivalent operation of ==, so Double does so as well. Since in fact the two numbers that you are generating in your test (the literal 0.5d and the 5x sum of .1d) are not ==
equal (the actual values in the processors' registers are different) Equals() returns false.
It is not the framework's intent to break the generally accepted rules of computing in order to make your life convenient.
Finally, I'd offer that NUnit has indeed realized this problem and according to http://www.nunit.org/index.php?p=equalConstraint&r=2.5 offers the following method to test floating point equality within a tolerance:
Assert.That( 5.0, Is.EqualTo( 5 );
Assert.That( 5.5, Is.EqualTo( 5 ).Within(0.075);
Assert.That( 5.5, Is.EqualTo( 5 ).Within(1.5).Percent;
回答4:
Assert.AreEqual
does take that into account.
But in order to do so, you need to supply your margin of error - the delta within the difference between the two float values are deemed equal for your application.
There are two overloads to Assert.AreEqual
that take only two parameters - a generic one (T, T)
and a non generic one - (object, object)
. These can only do the default comparisons.
Use one of the overloads that take double
and that also has a parameter for the delta.
回答5:
This is the feature of computer floating point arithmetics
(http://www.eskimo.com/~scs/cclass/progintro/sx5.html)
It's important to remember that the precision of floating-point
numbers is usually limited, and this can lead to surprising results.
The result of a division like 1/3 cannot be represented exactly (it's
an infinitely repeating fraction, 0.333333...), so the computation (1
/ 3) x 3 tends to yield a result like 0.999999... instead of 1.0.
Furthermore, in base 2, the fraction 1/10, or 0.1 in decimal, is also
an infinitely repeating fraction, and cannot be represented exactly,
either, so (1 / 10) x 10 may also yield 0.999999.... For these reasons
and others, floating-point calculations are rarely exact. When working
with computer floating point, you have to be careful not to compare
two numbers for exact equality, and you have to ensure that ``round
off error'' doesn't accumulate until it seriously degrades the results
of your calculations.
You should explicit set the precision for Assert
For example:
double precision = 1e-6;
Assert.AreEqual(d, 1.0, precision);
It's work for you sample. I often use this way in my code, but precision depending on the situation
回答6:
This is because floating point numbers lose precision. The best way to compare equals is to subtract the numbers and verify the different is less then a certain number such as .001 (or to whatever precision you need). Look at http://msdn.microsoft.com/en-us/library/system.double%28v=VS.95%29.aspx specifically the Floating-Point Values and Loss of Precision section.
回答7:
0.1
can't be represented exactly in a double because of it's internal format.
Use decimal if you want to represent base 10 numbers.
If you want to compare doubles check whether they are within a very small amount of each other.