有人可以指向(或显示)一些不错的C#通用浮点比较函数来比较浮点值吗?我想实施的功能IsEqual
,IsGreater
一个IsLess
。我也只真正关心双打而不是浮动。
有人可以指向(或显示)一些不错的C#通用浮点比较函数来比较浮点值吗?我想实施的功能IsEqual
,IsGreater
一个IsLess
。我也只真正关心双打而不是浮动。
Answers:
编写有用的通用浮点IsEqual
非常非常困难,即使不是完全不可能的。您当前的代码将因严重失败a==0
。在这种情况下,该方法的行为确实是一个定义问题,并且可以说,最好针对特定领域用例量身定制代码。
对于这种事情,您确实非常需要一个好的测试套件。这就是我在《浮点指南》中所做的事情,这是我最终想到的(Java代码应该足够容易翻译):
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || absA + absB < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
附录: c#中相同的代码用于双打(按问题要求)
public static bool NearlyEqual(double a, double b, double epsilon)
{
const double MinNormal = 2.2250738585072014E-308d;
double absA = Math.Abs(a);
double absB = Math.Abs(b);
double diff = Math.Abs(a - b);
if (a.Equals(b))
{ // shortcut, handles infinities
return true;
}
else if (a == 0 || b == 0 || absA + absB < MinNormal)
{
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * MinNormal);
}
else
{ // use relative error
return diff / (absA + absB) < epsilon;
}
}
+float.Epsilon
并且-float.Epsilon
不被视为相等,因为它们是可表示的最小浮点值,不为零。这显然不同于您描述的行为Float.MIN_VALUE
。您认为此功能在C#中是否可行?此页面上的另一个人也遇到相同的问题:stackoverflow.com/questions/3874627/…–
AlmostEqual(1.401298E-20f, -1.401298E-20f, 1E-12f)
我希望它也会通过,1E-19f
但不会通过1E-20f
。我对吗?如果没有我在粘贴中建议的额外检查[1],否则我仍然会进行其他失败的测试。
Double.MinValue
不执行所需的操作。它返回具有最大可能绝对值的负数-1.7976931348623157E+308
。 double.Epsiilon
看起来对应于Float.Min_Value
; 没有等价的东西Min_Normal
。也许您想要类似的东西(1e7)*double.Epsilon
?
从Bruce Dawson的有关比较浮点数的论文中,您还可以将浮点数作为整数进行比较。紧密度由最低有效位决定。
public static bool AlmostEqual2sComplement( float a, float b, int maxDeltaBits )
{
int aInt = BitConverter.ToInt32( BitConverter.GetBytes( a ), 0 );
if ( aInt < 0 )
aInt = Int32.MinValue - aInt; // Int32.MinValue = 0x80000000
int bInt = BitConverter.ToInt32( BitConverter.GetBytes( b ), 0 );
if ( bInt < 0 )
bInt = Int32.MinValue - bInt;
int intDiff = Math.Abs( aInt - bInt );
return intDiff <= ( 1 << maxDeltaBits );
}
编辑:BitConverter相对较慢。如果您愿意使用不安全的代码,那么以下是一个非常快速的版本:
public static unsafe int FloatToInt32Bits( float f )
{
return *( (int*)&f );
}
public static bool AlmostEqual2sComplement( float a, float b, int maxDeltaBits )
{
int aInt = FloatToInt32Bits( a );
if ( aInt < 0 )
aInt = Int32.MinValue - aInt;
int bInt = FloatToInt32Bits( b );
if ( bInt < 0 )
bInt = Int32.MinValue - bInt;
int intDiff = Math.Abs( aInt - bInt );
return intDiff <= ( 1 << maxDeltaBits );
}
maxDeltaBits
从而使该函数的工作原理与之相似(但当然更准确)abs(a - b) < delta
?我喜欢这种方法的想法,但是我更喜欢一个可以指定最大绝对误差的函数。
进一步了解Andrew Wang的答案:如果BitConverter方法太慢,但是您不能在项目中使用不安全的代码,则此结构比BitConverter快6倍:
[StructLayout(LayoutKind.Explicit)]
public struct FloatToIntSafeBitConverter
{
public static int Convert(float value)
{
return new FloatToIntSafeBitConverter(value).IntValue;
}
public FloatToIntSafeBitConverter(float floatValue): this()
{
FloatValue = floatValue;
}
[FieldOffset(0)]
public readonly int IntValue;
[FieldOffset(0)]
public readonly float FloatValue;
}
(顺便说一句,我尝试使用接受的解决方案,但它(至少是我的转换)未能通过答案中也提到的某些单元测试。例如assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE));
)
继迈克尔和测试提供的答案之后,将原始Java代码转换为C#时要记住的重要一点是Java和C#定义了不同的常量。例如,C#缺少Java的MIN_NORMAL,并且MinValue的定义差异很大。
Java将MIN_VALUE定义为可能的最小正值,而C#将其定义为总体上最小的可表示值。C#中的等效值为Epsilon。
缺少MIN_NORMAL对于直接转换原始算法会造成问题-如果没有它,则对于零附近的较小值,事情会开始崩溃。Java的MIN_NORMAL遵循IEEE最小可能数的规范,而有效位的前导位不为零,因此请记住,我们可以为单打和双打定义自己的法线(在原始答案的注释中提到了dbc )。
以下针对单打的C#代码通过了《浮点指南》中给出的所有测试,而双打的版本通过了对测试用例进行的少量修改以说明提高的精度的所有测试。
public static bool ApproximatelyEqualEpsilon(float a, float b, float epsilon)
{
const float floatNormal = (1 << 23) * float.Epsilon;
float absA = Math.Abs(a);
float absB = Math.Abs(b);
float diff = Math.Abs(a - b);
if (a == b)
{
// Shortcut, handles infinities
return true;
}
if (a == 0.0f || b == 0.0f || diff < floatNormal)
{
// a or b is zero, or both are extremely close to it.
// relative error is less meaningful here
return diff < (epsilon * floatNormal);
}
// use relative error
return diff / Math.Min((absA + absB), float.MaxValue) < epsilon;
}
除了类型更改外,双打的版本是相同的,而法线的定义是这样的。
const double doubleNormal = (1L << 52) * double.Epsilon;
注意一些答案...
更新2019-0829,我还包括了Microsoft反编译的代码,它应该比我的要好得多。
1-您可以轻松地在内存中用双精度数表示任何包含15个有效数字的数字。参见维基百科。
2-问题来自浮点数的计算,您可能在其中失去一些精度。我的意思是,像.1这样的数字在计算后可能变成像.1000000000000001 ==>之类的东西。当您进行一些计算时,结果可能会被截断以便用双精度表示。截断带来了您可能会得到的错误。
3-为了防止在比较双精度值时出现问题,人们引入了误差容限,通常称为epsilon。如果2个浮点数只有上下文的epsilon作为差值,则将它们视为相等。double.Epsilon是double值与其neigbor(下一个或上一个)值之间的最小数字。
4-2个double值之间的差可能大于double.epsilon。实际double值和计算出的double值之间的差异取决于您进行了多少次计算以及进行了哪些计算。许多人认为它总是双倍Epsilon,但他们确实错了。要获得很好的答案,请参阅:Hans Passant答案。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:
// /programming/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:
// /programming/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;
}
// ******************************************************************
}
1e-15
就是在很大的方面。当将n / n乘以1 / n时,甚至1e-15的epsilon在1到1e6的n中的999282中也“失败”。埃里克·利珀特(Eric Lippert)挑选您的epsilon似乎是解决问题的另一种方法。
这是我使用可为空的双扩展方法解决问题的方法。
public static bool NearlyEquals(this double? value1, double? value2, double unimportantDifference = 0.0001)
{
if (value1 != value2)
{
if(value1 == null || value2 == null)
return false;
return Math.Abs(value1.Value - value2.Value) < unimportantDifference;
}
return true;
}
...
double? value1 = 100;
value1.NearlyEquals(100.01); // will return false
value1.NearlyEquals(100.000001); // will return true
value1.NearlyEquals(100.01, 0.1); // will return true
这是西蒙·休伊特课程的扩展版本:
/// <summary>
/// Safely converts a <see cref="float"/> to an <see cref="int"/> for floating-point comparisons.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct FloatToInt : IEquatable<FloatToInt>, IEquatable<float>, IEquatable<int>, IComparable<FloatToInt>, IComparable<float>, IComparable<int>
{
/// <summary>
/// Initializes a new instance of the <see cref="FloatToInt"/> class.
/// </summary>
/// <param name="floatValue">The <see cref="float"/> value to be converted to an <see cref="int"/>.</param>
public FloatToInt(float floatValue)
: this()
{
FloatValue = floatValue;
}
/// <summary>
/// Gets the floating-point value as an integer.
/// </summary>
[FieldOffset(0)]
public readonly int IntValue;
/// <summary>
/// Gets the floating-point value.
/// </summary>
[FieldOffset(0)]
public readonly float FloatValue;
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(FloatToInt other)
{
return other.IntValue == IntValue;
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(float other)
{
return IntValue == new FloatToInt(other).IntValue;
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(int other)
{
return IntValue == other;
}
/// <summary>
/// Compares the current object with another object of the same type.
/// </summary>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the <paramref name="other"/> parameter.Zero This object is equal to <paramref name="other"/>. Greater than zero This object is greater than <paramref name="other"/>.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public int CompareTo(FloatToInt other)
{
return IntValue.CompareTo(other.IntValue);
}
/// <summary>
/// Compares the current object with another object of the same type.
/// </summary>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the <paramref name="other"/> parameter.Zero This object is equal to <paramref name="other"/>. Greater than zero This object is greater than <paramref name="other"/>.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public int CompareTo(float other)
{
return IntValue.CompareTo(new FloatToInt(other).IntValue);
}
/// <summary>
/// Compares the current object with another object of the same type.
/// </summary>
/// <returns>
/// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the <paramref name="other"/> parameter.Zero This object is equal to <paramref name="other"/>. Greater than zero This object is greater than <paramref name="other"/>.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public int CompareTo(int other)
{
return IntValue.CompareTo(other);
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
/// <param name="obj">Another object to compare to. </param><filterpriority>2</filterpriority>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (obj.GetType() != typeof(FloatToInt))
{
return false;
}
return Equals((FloatToInt)obj);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
/// <filterpriority>2</filterpriority>
public override int GetHashCode()
{
return IntValue;
}
/// <summary>
/// Implicitly converts from a <see cref="FloatToInt"/> to an <see cref="int"/>.
/// </summary>
/// <param name="value">A <see cref="FloatToInt"/>.</param>
/// <returns>An integer representation of the floating-point value.</returns>
public static implicit operator int(FloatToInt value)
{
return value.IntValue;
}
/// <summary>
/// Implicitly converts from a <see cref="FloatToInt"/> to a <see cref="float"/>.
/// </summary>
/// <param name="value">A <see cref="FloatToInt"/>.</param>
/// <returns>The floating-point value.</returns>
public static implicit operator float(FloatToInt value)
{
return value.FloatValue;
}
/// <summary>
/// Determines if two <see cref="FloatToInt"/> instances have the same integer representation.
/// </summary>
/// <param name="left">A <see cref="FloatToInt"/>.</param>
/// <param name="right">A <see cref="FloatToInt"/>.</param>
/// <returns>true if the two <see cref="FloatToInt"/> have the same integer representation; otherwise, false.</returns>
public static bool operator ==(FloatToInt left, FloatToInt right)
{
return left.IntValue == right.IntValue;
}
/// <summary>
/// Determines if two <see cref="FloatToInt"/> instances have different integer representations.
/// </summary>
/// <param name="left">A <see cref="FloatToInt"/>.</param>
/// <param name="right">A <see cref="FloatToInt"/>.</param>
/// <returns>true if the two <see cref="FloatToInt"/> have different integer representations; otherwise, false.</returns>
public static bool operator !=(FloatToInt left, FloatToInt right)
{
return !(left == right);
}
}
我认为您的第二选择是最好的选择。通常,在浮点比较中,您通常只关心一个值在另一个值的一定公差范围内,这取决于选择epsilon。
尽管第二个选项比较笼统,但是当您具有绝对公差并且必须执行许多这样的比较时,第一个选项会更好。如果说对图像中的每个像素进行此比较,则第二个选项中的相乘可能会将您的执行速度减慢到无法接受的性能水平。
我翻译了迈克尔·伯格沃德(Michael Borgwardt)的样本。结果如下:
public static bool NearlyEqual(float a, float b, float epsilon){
float absA = Math.Abs (a);
float absB = Math.Abs (b);
float diff = Math.Abs (a - b);
if (a == b) {
return true;
} else if (a == 0 || b == 0 || diff < float.Epsilon) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < epsilon;
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
随时改进此答案。
float.MinValue
是可以由float
数据类型表示的最小值,而不是可以由表示的最小正值float
。
static class FloatUtil {
static bool IsEqual(float a, float b, float tolerance = 0.001f) {
return Math.Abs(a - b) < tolerance;
}
static bool IsGreater(float a, float b) {
return a > b;
}
static bool IsLess(float a, float b) {
return a < b;
}
}
客户可以决定将其值tolerance
传递给IsEqual
谁。
IsEqual(1.002, 1.001); --> False
IsEqual(1.002, 1.001, 0.01); --> True