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>
|
/// <summary>
|
||||||
/// Verifies that the negative floating point zero is within one ulp of the positive
|
/// Verifies that two denormalized floats can be compared in ulps
|
||||||
/// floating point zero and vice versa
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void NegativeZeroFloatEqualsPositiveZero() {
|
public void DenormalizedFloatsCanBeCompared() {
|
||||||
float zero = 0.0f;
|
float zero = 0.0f;
|
||||||
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
|
float zeroPlusOneUlp = FloatHelper.ReinterpretAsFloat(
|
||||||
FloatHelper.ReinterpretAsInt(zero) + 1
|
FloatHelper.ReinterpretAsInt(zero) + 1
|
||||||
);
|
);
|
||||||
float zeroMinusOneUlp = -zeroPlusOneUlp;
|
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.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 0));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
||||||
|
@ -174,18 +229,34 @@ namespace Nuclex.Support {
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void NegativeZeroDoubleEqualsPositiveZero() {
|
public void NegativeZeroDoubleEqualsPositiveZero() {
|
||||||
double zero = 0.0;
|
Assert.IsTrue(
|
||||||
double zeroPlusOneUlp = FloatHelper.ReinterpretAsDouble(
|
FloatHelper.AreAlmostEqual(
|
||||||
FloatHelper.ReinterpretAsLong(zero) + 1
|
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(-tenUlps, tenUlps, 20));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroPlusOneUlp, 1));
|
Assert.IsTrue(FloatHelper.AreAlmostEqual(tenUlps, -tenUlps, 20));
|
||||||
Assert.IsFalse(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 0));
|
Assert.IsFalse(FloatHelper.AreAlmostEqual(-tenUlps, tenUlps, 19));
|
||||||
Assert.IsTrue(FloatHelper.AreAlmostEqual(zero, zeroMinusOneUlp, 1));
|
|
||||||
|
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>
|
/// </remarks>
|
||||||
public static class FloatHelper {
|
public static class FloatHelper {
|
||||||
|
|
||||||
#region struct FloatIntUnion
|
#region struct FloatInt32Union
|
||||||
|
|
||||||
/// <summary>Union of a floating point variable and an integer</summary>
|
/// <summary>Union of a floating point variable and an integer</summary>
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
private struct FloatIntUnion {
|
private struct FloatInt32Union {
|
||||||
|
|
||||||
/// <summary>The union's value as a floating point variable</summary>
|
/// <summary>The union's value as a floating point variable</summary>
|
||||||
[FieldOffset(0)]
|
[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>
|
/// <summary>Union of a double precision floating point variable and a long</summary>
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
private struct DoubleLongUnion {
|
private struct DoubleInt64Union {
|
||||||
|
|
||||||
/// <summary>The union's value as a double precision floating point variable</summary>
|
/// <summary>The union's value as a double precision floating point variable</summary>
|
||||||
[FieldOffset(0)]
|
[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>
|
/// <summary>A floating point value that holds a positive zero</summary>
|
||||||
public const float PositiveZeroFloat = +0.0f;
|
public const float PositiveZeroFloat = +0.0f;
|
||||||
|
@ -145,11 +145,30 @@ namespace Nuclex.Support {
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Implementation partially follows the code outlined here (link now defunct):
|
/// Implementation partially follows the code outlined here (link now defunct):
|
||||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
/// 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>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static bool AreAlmostEqual(float left, float right, int maxUlps) {
|
public static bool AreAlmostEqual(float left, float right, int maxUlps) {
|
||||||
FloatIntUnion leftUnion = new FloatIntUnion();
|
var leftUnion = new FloatInt32Union();
|
||||||
FloatIntUnion rightUnion = new FloatIntUnion();
|
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;
|
leftUnion.Float = left;
|
||||||
rightUnion.Float = right;
|
rightUnion.Float = right;
|
||||||
|
@ -165,6 +184,7 @@ namespace Nuclex.Support {
|
||||||
|
|
||||||
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
|
return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>Compares two double precision floating point values for equality</summary>
|
/// <summary>Compares two double precision floating point values for equality</summary>
|
||||||
/// <param name="left">First double precision floating point value to be compared</param>
|
/// <param name="left">First double precision floating point value to be compared</param>
|
||||||
|
@ -189,11 +209,30 @@ namespace Nuclex.Support {
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Implementation partially follows the code outlined here:
|
/// Implementation partially follows the code outlined here:
|
||||||
/// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/
|
/// 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>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static bool AreAlmostEqual(double left, double right, long maxUlps) {
|
public static bool AreAlmostEqual(double left, double right, long maxUlps) {
|
||||||
DoubleLongUnion leftUnion = new DoubleLongUnion();
|
var leftUnion = new DoubleInt64Union();
|
||||||
DoubleLongUnion rightUnion = new DoubleLongUnion();
|
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;
|
leftUnion.Double = left;
|
||||||
rightUnion.Double = right;
|
rightUnion.Double = right;
|
||||||
|
@ -209,6 +248,7 @@ namespace Nuclex.Support {
|
||||||
|
|
||||||
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
|
return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reinterprets the memory contents of a floating point value as an integer value
|
/// 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
|
/// The memory contents of the floating point value interpreted as an integer
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static int ReinterpretAsInt(this float value) {
|
public static int ReinterpretAsInt(this float value) {
|
||||||
FloatIntUnion union = new FloatIntUnion();
|
FloatInt32Union union = new FloatInt32Union();
|
||||||
union.Float = value;
|
union.Float = value;
|
||||||
return union.Int;
|
return union.Int;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +277,7 @@ namespace Nuclex.Support {
|
||||||
/// interpreted as an integer
|
/// interpreted as an integer
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static long ReinterpretAsLong(this double value) {
|
public static long ReinterpretAsLong(this double value) {
|
||||||
DoubleLongUnion union = new DoubleLongUnion();
|
DoubleInt64Union union = new DoubleInt64Union();
|
||||||
union.Double = value;
|
union.Double = value;
|
||||||
return union.Long;
|
return union.Long;
|
||||||
}
|
}
|
||||||
|
@ -250,7 +290,7 @@ namespace Nuclex.Support {
|
||||||
/// The memory contents of the integer value interpreted as a floating point value
|
/// The memory contents of the integer value interpreted as a floating point value
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static float ReinterpretAsFloat(this int value) {
|
public static float ReinterpretAsFloat(this int value) {
|
||||||
FloatIntUnion union = new FloatIntUnion();
|
FloatInt32Union union = new FloatInt32Union();
|
||||||
union.Int = value;
|
union.Int = value;
|
||||||
return union.Float;
|
return union.Float;
|
||||||
}
|
}
|
||||||
|
@ -265,7 +305,7 @@ namespace Nuclex.Support {
|
||||||
/// floating point value
|
/// floating point value
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static double ReinterpretAsDouble(this long value) {
|
public static double ReinterpretAsDouble(this long value) {
|
||||||
DoubleLongUnion union = new DoubleLongUnion();
|
DoubleInt64Union union = new DoubleInt64Union();
|
||||||
union.Long = value;
|
union.Long = value;
|
||||||
return union.Double;
|
return union.Double;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user