diff --git a/Source/FloatHelper.Test.cs b/Source/FloatHelper.Test.cs
index 15a27c5..5e1bc5b 100644
--- a/Source/FloatHelper.Test.cs
+++ b/Source/FloatHelper.Test.cs
@@ -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
///
- /// 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
///
[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));
+ }
+
+ ///
+ /// Verifies that the negative floating point zero is within one ulp of the positive
+ /// floating point zero and vice versa
+ ///
+ [Test]
+ public void NegativeZeroFloatEqualsPositiveZero() {
+ Assert.IsTrue(
+ FloatHelper.AreAlmostEqual(
+ FloatHelper.NegativeZeroFloat, FloatHelper.PositiveZeroFloat, 0
+ )
+ );
+ Assert.IsTrue(
+ FloatHelper.AreAlmostEqual(
+ FloatHelper.PositiveZeroFloat, FloatHelper.NegativeZeroFloat, 0
+ )
+ );
+ }
+
+ /// Verifies that floats can be compared across the zero boundary
+ [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));
+ }
+
+ ///
+ /// Verifies that two denormalized doubles can be compared in ulps
+ ///
+ [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 {
///
[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);
+ /// Verifies that doubles can be compared across the zero boundary
+ [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));
}
}
diff --git a/Source/FloatHelper.cs b/Source/FloatHelper.cs
index 0833753..1a09609 100644
--- a/Source/FloatHelper.cs
+++ b/Source/FloatHelper.cs
@@ -44,11 +44,11 @@ namespace Nuclex.Support {
///
public static class FloatHelper {
- #region struct FloatIntUnion
+ #region struct FloatInt32Union
/// Union of a floating point variable and an integer
[StructLayout(LayoutKind.Explicit)]
- private struct FloatIntUnion {
+ private struct FloatInt32Union {
/// The union's value as a floating point variable
[FieldOffset(0)]
@@ -64,13 +64,13 @@ namespace Nuclex.Support {
}
- #endregion // struct FloatIntUnion
+ #endregion // struct FloatInt32Union
- #region struct DoubleLongUnion
+ #region struct DoubleInt64Union
/// Union of a double precision floating point variable and a long
[StructLayout(LayoutKind.Explicit)]
- private struct DoubleLongUnion {
+ private struct DoubleInt64Union {
/// The union's value as a double precision floating point variable
[FieldOffset(0)]
@@ -86,7 +86,7 @@ namespace Nuclex.Support {
}
- #endregion // struct DoubleLongUnion
+ #endregion // struct DoubleInt64Union
/// A floating point value that holds a positive zero
public const float PositiveZeroFloat = +0.0f;
@@ -145,11 +145,30 @@ namespace Nuclex.Support {
///
/// 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/
///
///
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
/// Compares two double precision floating point values for equality
/// First double precision floating point value to be compared
@@ -189,11 +209,30 @@ namespace Nuclex.Support {
///
/// 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/
///
///
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
///
/// 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
///
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
///
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
///
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
///
public static double ReinterpretAsDouble(this long value) {
- DoubleLongUnion union = new DoubleLongUnion();
+ DoubleInt64Union union = new DoubleInt64Union();
union.Long = value;
return union.Double;
}