From 1132bc5681c8f2a6390d6b71913a173de9c88590 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Mon, 26 May 2008 20:04:33 +0000 Subject: [PATCH] Added helper class for floating point operations; implemented advanced floating point comparison git-svn-id: file:///srv/devel/repo-conversion/nusu@72 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (x86).csproj | 4 + Source/FloatHelper.Test.cs | 66 ++++++++++++++++ Source/FloatHelper.cs | 148 ++++++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 Source/FloatHelper.Test.cs create mode 100644 Source/FloatHelper.cs diff --git a/Nuclex.Support (x86).csproj b/Nuclex.Support (x86).csproj index dcc9a8d..0a0e8ab 100644 --- a/Nuclex.Support (x86).csproj +++ b/Nuclex.Support (x86).csproj @@ -108,6 +108,10 @@ TransformingReadOnlyCollection.cs + + + FloatHelper.cs + LicenseKey.cs diff --git a/Source/FloatHelper.Test.cs b/Source/FloatHelper.Test.cs new file mode 100644 index 0000000..d84e752 --- /dev/null +++ b/Source/FloatHelper.Test.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; + +#if UNITTEST + +using NUnit.Framework; + +namespace Nuclex.Support { + + /// Unit Test for the FloatHelper class + [TestFixture] + public class FloatHelperTest { + + /// Tests the floating point value comparison helper + [Test] + public void TestFloatComparison() { + Assert.IsTrue( + FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000008f, 1), + "Minimal difference between very small floating point numbers is considered equal" + ); + Assert.IsFalse( + FloatHelper.AreAlmostEqual(0.00000001f, 0.0000000100000017f, 1), + "Larger difference between very small floating point numbers is not considered equal" + ); + + Assert.IsTrue( + FloatHelper.AreAlmostEqual(1000000.00f, 1000000.06f, 1), + "Minimal difference between very large floating point numbers is considered equal" + ); + Assert.IsFalse( + FloatHelper.AreAlmostEqual(1000000.00f, 1000000.13f, 1), + "Larger difference between very large floating point numbers is not considered equal" + ); + } + + /// Tests the double precision floating point value comparison helper + [Test] + public void TestDoubleComparison() { + Assert.IsTrue( + FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000002, 1), + "Minimal difference between very small double precision floating point " + + "numbers is considered equal" + ); + Assert.IsFalse( + FloatHelper.AreAlmostEqual(0.00000001, 0.000000010000000000000004, 1), + "Larger difference between very small double precision floating point " + + "numbers is not considered equal" + ); + + Assert.IsTrue( + FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000001, 1), + "Minimal difference between very large double precision floating point " + + "numbers is considered equal" + ); + Assert.IsFalse( + FloatHelper.AreAlmostEqual(1000000.00, 1000000.0000000002, 1), + "Larger difference between very large double precision floating point " + + "numbers is not considered equal" + ); + } + + } + +} // namespace Nuclex.Support + +#endif // UNITTEST \ No newline at end of file diff --git a/Source/FloatHelper.cs b/Source/FloatHelper.cs new file mode 100644 index 0000000..412ac98 --- /dev/null +++ b/Source/FloatHelper.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Nuclex.Support { + + /// Helper routines for working with floating point numbers + /// + /// The floating point comparison code is based on this excellent article: + /// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm + /// + public static class FloatHelper { + + #region struct FloatIntUnion + + /// Union of a floating point variable and an integer + [StructLayout(LayoutKind.Explicit)] + private struct FloatIntUnion { + + /// The union's value as a floating point variable + [FieldOffset(0)] + public float Float; + + /// The union's value as an integer + [FieldOffset(0)] + public int Int; + + /// The union's value as an unsigned integer + [FieldOffset(0)] + public uint UInt; + + } + + #endregion // struct FloatIntUnion + + #region struct DoubleLongUnion + + /// Union of a double precision floating point variable and a long + [StructLayout(LayoutKind.Explicit)] + private struct DoubleLongUnion { + + /// The union's value as a double precision floating point variable + [FieldOffset(0)] + public double Double; + + /// The union's value as a long + [FieldOffset(0)] + public long Long; + + /// The union's value as an unsigned long + [FieldOffset(0)] + public ulong ULong; + + } + + #endregion // struct DoubleLongUnion + + /// Compares two floating point values for equality + /// First floating point value to be compared + /// Second floating point value t be compared + /// + /// Maximum number of representable floating point values that are allowed to + /// be between the left and the right floating point values + /// + /// True if both numbers are equal or close to being equal + /// + /// + /// Floating point values can only represent a limited series of natural numbers. + /// For example, the values 2.00000000 and 2.00000024 can be stored in a float, + /// but nothing inbetween them. + /// + /// + /// This comparison will count how many possible floating point values are between + /// the left and the right number. If the number of possible values between both + /// numbers is less than or equal to maxUlps, then the numbers are considered as + /// being equal. + /// + /// + /// Implementation partially follows the code outlined here: + /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ + /// + /// + public static bool AreAlmostEqual(float left, float right, int maxUlps) { + FloatIntUnion leftUnion = new FloatIntUnion(); + FloatIntUnion rightUnion = new FloatIntUnion(); + + leftUnion.Float = left; + rightUnion.Float = right; + + uint leftSignMask = (leftUnion.UInt >> 31); + uint rightSignMask = (rightUnion.UInt >> 31); + + uint leftTemp = ((0x80000000 - leftUnion.UInt) & leftSignMask); + leftUnion.UInt = leftTemp | (leftUnion.UInt & ~leftSignMask); + + uint rightTemp = ((0x80000000 - rightUnion.UInt) & rightSignMask); + rightUnion.UInt = rightTemp | (rightUnion.UInt & ~rightSignMask); + + return (Math.Abs(leftUnion.Int - rightUnion.Int) <= maxUlps); + } + + /// Compares two double precision floating point values for equality + /// First double precision floating point value to be compared + /// Second double precision floating point value t be compared + /// + /// Maximum number of representable double precision floating point values that are + /// allowed to be between the left and the right double precision floating point values + /// + /// True if both numbers are equal or close to being equal + /// + /// + /// Double precision floating point values can only represent a limited series of + /// natural numbers. For example, the values 2.0000000000000000 and 2.0000000000000004 + /// can be stored in a double, but nothing inbetween them. + /// + /// + /// This comparison will count how many possible double precision floating point + /// values are between the left and the right number. If the number of possible + /// values between both numbers is less than or equal to maxUlps, then the numbers + /// are considered as being equal. + /// + /// + /// Implementation partially follows the code outlined here: + /// http://www.anttirt.net/2007/08/19/proper-floating-point-comparisons/ + /// + /// + public static bool AreAlmostEqual(double left, double right, long maxUlps) { + DoubleLongUnion leftUnion = new DoubleLongUnion(); + DoubleLongUnion rightUnion = new DoubleLongUnion(); + + leftUnion.Double = left; + rightUnion.Double = right; + + ulong leftSignMask = (leftUnion.ULong >> 63); + ulong rightSignMask = (rightUnion.ULong >> 63); + + ulong leftTemp = ((0x8000000000000000 - leftUnion.ULong) & leftSignMask); + leftUnion.ULong = leftTemp | (leftUnion.ULong & ~leftSignMask); + + ulong rightTemp = ((0x8000000000000000 - rightUnion.ULong) & rightSignMask); + rightUnion.ULong = rightTemp | (rightUnion.ULong & ~rightSignMask); + + return (Math.Abs(leftUnion.Long - rightUnion.Long) <= maxUlps); + } + + } + +} // namespace Nuclex.Support