小心一些答案...
UPDATE 2019-0829,我还包含了微软反编译的代码,应该比我的要好得多。
1 - 您可以轻松地用双精度表示内存中具有 15 个有效数字的任何数字。见Wikipedia。
2 - 问题来自浮点数的计算,您可能会失去一些精度。我的意思是像 .1 这样的数字在计算后可能会变成像 .1000000000000001 ==> 这样的数字。当您进行一些计算时,结果可能会被截断以便以双精度表示。这种截断会带来你可能得到的错误。
3 - 为了防止在比较双精度值时出现问题,人们引入了通常称为 epsilon 的误差范围。如果 2 个浮点数只有一个上下文 epsilon 作为差异,那么它们被认为是相等的。 double.Epsilon 是双精度值与其相邻(下一个或上一个)值之间的最小数字。
4 - 2 个 double 值之间的差异可能大于 double.epsilon。实际双精度值与计算出的值之间的差异取决于您进行了多少次计算以及哪些计算。许多人认为它总是 double.Epsilon 但他们真的错了。要得到一个很好的答案,请参阅:Hans Passant answer。 epsilon 基于您的上下文,它取决于您在计算期间达到的最大数字以及您正在执行的计算次数(截断误差累积)。
5 - 这是我使用的代码。请注意,我仅将我的 epsilon 用于少数计算。否则,我将我的 epsilon 乘以 10 或 100。
6 - 正如 SvenL 所指出的,我的 epsilon 可能不够大。我建议阅读 SvenL 评论。另外,也许“十进制”可以为您的情况做这项工作?
微软反编译代码:
// Decompiled with JetBrains decompiler
// Type: MS.Internal.DoubleUtil
// Assembly: WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: 33C590FB-77D1-4FFD-B11B-3D104CA038E5
// Assembly location: C:\Windows\Microsoft.NET\assembly\GAC_MSIL\WindowsBase\v4.0_4.0.0.0__31bf3856ad364e35\WindowsBase.dll
using MS.Internal.WindowsBase;
using System;
using System.Runtime.InteropServices;
using System.Windows;
namespace MS.Internal
{
[FriendAccessAllowed]
internal static class DoubleUtil
{
internal const double DBL_EPSILON = 2.22044604925031E-16;
internal const float FLT_MIN = 1.175494E-38f;
public static bool AreClose(double value1, double value2)
{
if (value1 == value2)
return true;
double num1 = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.22044604925031E-16;
double num2 = value1 - value2;
if (-num1 < num2)
return num1 > num2;
return false;
}
public static bool LessThan(double value1, double value2)
{
if (value1 < value2)
return !DoubleUtil.AreClose(value1, value2);
return false;
}
public static bool GreaterThan(double value1, double value2)
{
if (value1 > value2)
return !DoubleUtil.AreClose(value1, value2);
return false;
}
public static bool LessThanOrClose(double value1, double value2)
{
if (value1 >= value2)
return DoubleUtil.AreClose(value1, value2);
return true;
}
public static bool GreaterThanOrClose(double value1, double value2)
{
if (value1 <= value2)
return DoubleUtil.AreClose(value1, value2);
return true;
}
public static bool IsOne(double value)
{
return Math.Abs(value - 1.0) < 2.22044604925031E-15;
}
public static bool IsZero(double value)
{
return Math.Abs(value) < 2.22044604925031E-15;
}
public static bool AreClose(Point point1, Point point2)
{
if (DoubleUtil.AreClose(point1.X, point2.X))
return DoubleUtil.AreClose(point1.Y, point2.Y);
return false;
}
public static bool AreClose(Size size1, Size size2)
{
if (DoubleUtil.AreClose(size1.Width, size2.Width))
return DoubleUtil.AreClose(size1.Height, size2.Height);
return false;
}
public static bool AreClose(Vector vector1, Vector vector2)
{
if (DoubleUtil.AreClose(vector1.X, vector2.X))
return DoubleUtil.AreClose(vector1.Y, vector2.Y);
return false;
}
public static bool AreClose(Rect rect1, Rect rect2)
{
if (rect1.IsEmpty)
return rect2.IsEmpty;
if (!rect2.IsEmpty && DoubleUtil.AreClose(rect1.X, rect2.X) && (DoubleUtil.AreClose(rect1.Y, rect2.Y) && DoubleUtil.AreClose(rect1.Height, rect2.Height)))
return DoubleUtil.AreClose(rect1.Width, rect2.Width);
return false;
}
public static bool IsBetweenZeroAndOne(double val)
{
if (DoubleUtil.GreaterThanOrClose(val, 0.0))
return DoubleUtil.LessThanOrClose(val, 1.0);
return false;
}
public static int DoubleToInt(double val)
{
if (0.0 >= val)
return (int) (val - 0.5);
return (int) (val + 0.5);
}
public static bool RectHasNaN(Rect r)
{
return DoubleUtil.IsNaN(r.X) || DoubleUtil.IsNaN(r.Y) || (DoubleUtil.IsNaN(r.Height) || DoubleUtil.IsNaN(r.Width));
}
public static bool IsNaN(double value)
{
DoubleUtil.NanUnion nanUnion = new DoubleUtil.NanUnion();
nanUnion.DoubleValue = value;
ulong num1 = nanUnion.UintValue & 18442240474082181120UL;
ulong num2 = nanUnion.UintValue & 4503599627370495UL;
if (num1 == 9218868437227405312UL || num1 == 18442240474082181120UL)
return num2 > 0UL;
return false;
}
[StructLayout(LayoutKind.Explicit)]
private struct NanUnion
{
[FieldOffset(0)]
internal double DoubleValue;
[FieldOffset(0)]
internal ulong UintValue;
}
}
}
我的代码:
public static class DoubleExtension
{
// ******************************************************************
// Base on Hans Passant Answer on:
// https://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre
/// <summary>
/// Compare two double taking in account the double precision potential error.
/// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
public static bool AboutEquals(this double value1, double value2)
{
double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15;
return Math.Abs(value1 - value2) <= epsilon;
}
// ******************************************************************
// Base on Hans Passant Answer on:
// https://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre
/// <summary>
/// Compare two double taking in account the double precision potential error.
/// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
/// You get really better performance when you can determine the contextual epsilon first.
/// </summary>
/// <param name="value1"></param>
/// <param name="value2"></param>
/// <param name="precalculatedContextualEpsilon"></param>
/// <returns></returns>
public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon)
{
return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon;
}
// ******************************************************************
public static double GetContextualEpsilon(this double biggestPossibleContextualValue)
{
return biggestPossibleContextualValue * 1E-15;
}
// ******************************************************************
/// <summary>
/// Mathlab equivalent
/// </summary>
/// <param name="dividend"></param>
/// <param name="divisor"></param>
/// <returns></returns>
public static double Mod(this double dividend, double divisor)
{
return dividend - System.Math.Floor(dividend / divisor) * divisor;
}
// ******************************************************************
}