Fixed the problems the AreAlmostEqual() methods had with floating point zeros
git-svn-id: file:///srv/devel/repo-conversion/nusu@294 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
parent
0a7306bb58
commit
5fb5b1f568
|
@ -143,25 +143,80 @@ namespace Nuclex.Support {
|
|||
);
|
||||
}
|
||||
|
||||
// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
||||
// Make both positive
|
||||
// If both are negative -> fine
|
||||
// If both are positive -> fine
|
||||
// If different -> Measure both distances to zero in ulps and sum them
|
||||
/// <summary>
|
||||
/// Verifies that the negative floating point zero is within one ulp of the positive
|
||||
/// floating point zero and vice versa
|
||||
/// Verifies that two denormalized floats can be compared in ulps
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NegativeZeroFloatEqualsPositiveZero() {
|
||||
public void DenormalizedFloatsCanBeCompared() {
|
||||
float zero = 0.0f;
|
||||
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
|
||||
FloatHelper.ReinterpretAsInt(zero) + 1
|
||||
);
|
||||
float zeroMinusOneUlp = -zeroPlusOneUlp;
|
||||
|
||||
bool test = FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1);
|
||||
// Across zero
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
||||
|
||||
// Against zero
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the negative floating point zero is within one ulp of the positive
|
||||
/// floating point zero and vice versa
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NegativeZeroFloatEqualsPositiveZero() {
|
||||
Assert.IsTrue(
|
||||
FloatHelper.AreAlmostEqual(
|
||||
FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
|
||||
)
|
||||
);
|
||||
Assert.IsTrue(
|
||||
FloatHelper.AreAlmostEqual(
|
||||
FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that floats can be compared across the zero boundary</summary>
|
||||
[Test]
|
||||
public void FloatsCanBeComparedAcrossZeroInUlps() {
|
||||
float tenUlps = float.Epsilon * 10.0f;
|
||||
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
||||
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
||||
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that two denormalized doubles can be compared in ulps
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void DenormalizedDoublesCanBeCompared() {
|
||||
double zero = 0.0;
|
||||
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
|
||||
FloatHelper.ReinterpretAsLong(zero) + 1
|
||||
);
|
||||
double zeroMinusOneUlp = -zeroPlusOneUlp;
|
||||
|
||||
// Across zero
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zeroPlusOneUlp, zeroMinusOneUlp, 2));
|
||||
|
||||
// Against zero
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||
|
@ -174,18 +229,34 @@ namespace Nuclex.Support {
|
|||
/// </summary>
|
||||
[Test]
|
||||
public void NegativeZeroDoubleEqualsPositiveZero() {
|
||||
double zero = 0.0;
|
||||
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
|
||||
FloatHelper.ReinterpretAsLong(zero) + 1
|
||||
Assert.IsTrue(
|
||||
FloatHelper.AreAlmostEqual(
|
||||
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
||||
)
|
||||
);
|
||||
double zeroMinusOneUlp = -zeroPlusOneUlp;
|
||||
Assert.IsTrue(
|
||||
FloatHelper.AreAlmostEqual(
|
||||
FloatHelper.NegativeZeroDouble, FloatHelper.NegativeZeroDouble, 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
bool test = FloatHelper.AreAlmostEqual(zeroMinusOneUlp, zeroPlusOneUlp, 1);
|
||||
/// <summary>Verifies that doubles can be compared across the zero boundary</summary>
|
||||
[Test]
|
||||
public void DoublesCanBeComparedAcrossZeroInUlps() {
|
||||
double tenUlps = double.Epsilon * 10.0;
|
||||
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 20));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
||||
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(-tenUlps, 0, 10));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, -tenUlps, 10));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, 0, 9));
|
||||
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(0, tenUlps, 10));
|
||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, 0, 10));
|
||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(0, tenUlps, 9));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,11 +44,11 @@ namespace Nuclex.Support {
|
|||
/// </remarks>
|
||||
public static class FloatHelper {
|
||||
|
||||
#region struct FloatIntUnion
|
||||
#region struct FloatInt32Union
|
||||
|
||||
/// <summary>Union of a floating point variable and an integer</summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct FloatIntUnion {
|
||||
private struct FloatInt32Union {
|
||||
|
||||
/// <summary>The union's value as a floating point variable</summary>
|
||||
[FieldOffset(0)]
|
||||
|
@ -64,13 +64,13 @@ namespace Nuclex.Support {
|
|||
|
||||
}
|
||||
|
||||
#endregion // struct FloatIntUnion
|
||||
#endregion // struct FloatInt32Union
|
||||
|
||||
#region struct DoubleLongUnion
|
||||
#region struct DoubleInt64Union
|
||||
|
||||
/// <summary>Union of a double precision floating point variable and a long</summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct DoubleLongUnion {
|
||||
private struct DoubleInt64Union {
|
||||
|
||||
/// <summary>The union's value as a double precision floating point variable</summary>
|
||||
[FieldOffset(0)]
|
||||
|
@ -86,7 +86,7 @@ namespace Nuclex.Support {
|
|||
|
||||
}
|
||||
|
||||
#endregion // struct DoubleLongUnion
|
||||
#endregion // struct DoubleInt64Union
|
||||
|
||||
/// <summary>A floating point value that holds a positive zero</summary>
|
||||
public const float PositiveZeroFloat = +0.0f;
|
||||
|
@ -145,11 +145,30 @@ namespace Nuclex.Support {
|
|||
/// <para>
|
||||
/// Implementation partially follows the code outlined here (link now defunct):
|
||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
||||
/// And here:
|
||||
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static bool AreAlmostEqual(float left, float right, int maxUlps) {
|
||||
FloatIntUnion leftUnion = new FloatIntUnion();
|
||||
FloatIntUnion rightUnion = new FloatIntUnion();
|
||||
var leftUnion = new FloatInt32Union();
|
||||
var rightUnion = new FloatInt32Union();
|
||||
|
||||
leftUnion.Float = left;
|
||||
rightUnion.Float = right;
|
||||
|
||||
if(leftUnion.Int < 0) {
|
||||
leftUnion.Int = unchecked((int)0x80000000 - leftUnion.Int);
|
||||
}
|
||||
if(rightUnion.Int < 0) {
|
||||
rightUnion.Int = unchecked((int)0x80000000 - rightUnion.Int);
|
||||
}
|
||||
|
||||
return Math.Abs(rightUnion.Int - leftUnion.Int) <= maxUlps;
|
||||
}
|
||||
#if false
|
||||
public static bool OldAreAlmostEqual(float left, float right, int maxUlps) {
|
||||
FloatInt32Union leftUnion = new FloatInt32Union();
|
||||
FloatInt32Union rightUnion = new FloatInt32Union();
|
||||
|
||||
leftUnion.Float = left;
|
||||
rightUnion.Float = right;
|
||||
|
@ -165,6 +184,7 @@ namespace Nuclex.Support {
|
|||
|
||||
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>Compares two double precision floating point values for equality</summary>
|
||||
/// <param name="left">First double precision floating point value to be compared</param>
|
||||
|
@ -189,11 +209,30 @@ namespace Nuclex.Support {
|
|||
/// <para>
|
||||
/// Implementation partially follows the code outlined here:
|
||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
||||
/// And here:
|
||||
/// http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static bool AreAlmostEqual(double left, double right, long maxUlps) {
|
||||
DoubleLongUnion leftUnion = new DoubleLongUnion();
|
||||
DoubleLongUnion rightUnion = new DoubleLongUnion();
|
||||
var leftUnion = new DoubleInt64Union();
|
||||
var rightUnion = new DoubleInt64Union();
|
||||
|
||||
leftUnion.Double = left;
|
||||
rightUnion.Double = right;
|
||||
|
||||
if(leftUnion.Long < 0) {
|
||||
leftUnion.Long = unchecked((long)0x8000000000000000 - leftUnion.Long);
|
||||
}
|
||||
if(rightUnion.Long < 0) {
|
||||
rightUnion.Long = unchecked((long)0x8000000000000000 - rightUnion.Long);
|
||||
}
|
||||
|
||||
return Math.Abs(rightUnion.Long - leftUnion.Long) <= maxUlps;
|
||||
}
|
||||
#if false
|
||||
public static bool OldAreAlmostEqual(double left, double right, long maxUlps) {
|
||||
DoubleInt64Union leftUnion = new DoubleInt64Union();
|
||||
DoubleInt64Union rightUnion = new DoubleInt64Union();
|
||||
|
||||
leftUnion.Double = left;
|
||||
rightUnion.Double = right;
|
||||
|
@ -209,6 +248,7 @@ namespace Nuclex.Support {
|
|||
|
||||
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Reinterprets the memory contents of a floating point value as an integer value
|
||||
|
@ -220,7 +260,7 @@ namespace Nuclex.Support {
|
|||
/// The memory contents of the floating point value interpreted as an integer
|
||||
/// </returns>
|
||||
public static int ReinterpretAsInt(this float value) {
|
||||
FloatIntUnion union = new FloatIntUnion();
|
||||
FloatInt32Union union = new FloatInt32Union();
|
||||
union.Float = value;
|
||||
return union.Int;
|
||||
}
|
||||
|
@ -237,7 +277,7 @@ namespace Nuclex.Support {
|
|||
/// interpreted as an integer
|
||||
/// </returns>
|
||||
public static long ReinterpretAsLong(this double value) {
|
||||
DoubleLongUnion union = new DoubleLongUnion();
|
||||
DoubleInt64Union union = new DoubleInt64Union();
|
||||
union.Double = value;
|
||||
return union.Long;
|
||||
}
|
||||
|
@ -250,7 +290,7 @@ namespace Nuclex.Support {
|
|||
/// The memory contents of the integer value interpreted as a floating point value
|
||||
/// </returns>
|
||||
public static float ReinterpretAsFloat(this int value) {
|
||||
FloatIntUnion union = new FloatIntUnion();
|
||||
FloatInt32Union union = new FloatInt32Union();
|
||||
union.Int = value;
|
||||
return union.Float;
|
||||
}
|
||||
|
@ -265,7 +305,7 @@ namespace Nuclex.Support {
|
|||
/// floating point value
|
||||
/// </returns>
|
||||
public static double ReinterpretAsDouble(this long value) {
|
||||
DoubleLongUnion union = new DoubleLongUnion();
|
||||
DoubleInt64Union union = new DoubleInt64Union();
|
||||
union.Long = value;
|
||||
return union.Double;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user