可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a simple example application here where I am multiplying and adding double
variables and then comparing them against an expected result. In both cases the result is equal to the expected result yet when I do the comparison it fails.
static void Main(string[] args)
{
double a = 98.1;
double b = 107.7;
double c = 92.5;
double d = 96.5;
double expectedResult = 88.5;
double result1 = (1*2*a) + (-1*1*b);
double result2 = (1*2*c) + (-1*1*d);
Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));
Console.Read();
}
And here is the output:
2x98.1 - 107.7 = 88.5
Equal to 88.5? False
2x92.5 - 96.5 = 88.5
Equal to 88.5? True
I need to be able to capture that it is in fact True
in BOTH cases. How would I do it?
回答1:
Floating point numbers often don't contain the exact value that mathematics tells us, because of how they store numbers.
To still have a reliable comparison, you need to allow some difference:
private const double DoubleEpsilon = 2.22044604925031E-16;
/// <summary>Determines whether <paramref name="value1"/> is very close to <paramref name="value2"/>.</summary>
/// <param name="value1">The value1.</param>
/// <param name="value2">The value2.</param>
/// <returns><c>true</c> if <paramref name="value1"/> is very close to value2; otherwise, <c>false</c>.</returns>
public static bool IsVeryCloseTo(this double value1, double value2)
{
if (value1 == value2)
return true;
var tolerance = (Math.Abs(value1) + Math.Abs(value2)) * DoubleEpsilon;
var difference = value1 - value2;
return -tolerance < difference && tolerance > difference;
}
Please also make sure to read this blog post.
回答2:
If you need more precision (for money and such) then use decimal
.
var a = 98.1M;
var b = 107.7M;
var c = 92.5M;
var d = 96.5M;
var expectedResult = 88.5M;
var result1 = (2 * a) + (-1 * b);
var result2 = (2 * c) + (-1 * d);
Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", a, b, result1, expectedResult == result1));
Console.WriteLine(String.Format("2x{0} - {1} = {2}\nEqual to 88.5? {3}\n", c, d, result2, expectedResult == result2));
Output:
2x98.1 - 107.7 = 88.5
Equal to 88.5? True
2x92.5 - 96.5 = 88.5
Equal to 88.5? True
回答3:
It's a problem with how floating point numbers are represented in memory.
You should read this to get a better understanding of whats going on: What Every Computer Scientist Should Know About Floating-Point Arithmetic
回答4:
Simply change your rounding to level 2 , this will give TRUE
double result1 =Math.Round ( (1 * 2 * a) + (-1 * 1 * b),2);
回答5:
using Math.Round() will round result1 to the correct decimal
result1 = Math.Round(result1, 1);
回答6:
using the debugger,
result1=88.499999999999986;
expectedResult = 88.5
So when using the double ,these are not equal.
回答7:
There is a whole school of thought that is against using Double.Epsilon
and similar numbers...
I think they use this: (taken from https://stackoverflow.com/a/2411661/613130 but modified with the checks for IsNaN
and IsInfinity
suggested here by nobugz
public static bool AboutEqual(double x, double y)
{
if (double.IsNaN(x)) return double.IsNaN(y);
if (double.IsInfinity(x)) return double.IsInfinity(y) && Math.Sign(x) == Math.Sign(y);
double epsilon = Math.Max(Math.Abs(x), Math.Abs(y)) * 1E-15;
return Math.Abs(x - y) <= epsilon;
}
The 1E-15
"magic number" is based on the fact that double
s have a little more than 15 digits of precision.
I'll add that for your numbers it returns true :-)