From fc5bd1b6ebdc91d2a43e49c07d45364b28f02b63 Mon Sep 17 00:00:00 2001 From: Christoph Ruegg Date: Mon, 27 Jul 2009 04:20:51 +0800 Subject: [PATCH] precision: added relative-error almost-equal Signed-off-by: Christoph Ruegg --- src/Managed.UnitTests/PrecisionTest.cs | 55 +++++++++++++++++++++++++- src/Managed/Precision.cs | 53 ++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/Managed.UnitTests/PrecisionTest.cs b/src/Managed.UnitTests/PrecisionTest.cs index d9f95c24..4b038ad1 100644 --- a/src/Managed.UnitTests/PrecisionTest.cs +++ b/src/Managed.UnitTests/PrecisionTest.cs @@ -485,13 +485,66 @@ namespace MathNet.Numerics.UnitTests [Test] public void AlmostEqual() { - AssertEx.That(()=>1.0.AlmostEqual(1.0), "1.0 equals 1.0."); + AssertEx.That(() => 1.0.AlmostEqual(1.0), "1.0 equals 1.0."); AssertEx.That(() => 1.0.AlmostEqual(1.0 + _doublePrecision), "1.0 equals 1.0 + 2^(-53)."); AssertEx.That(() => 1.0.AlmostEqual(1.0 + _doublePrecision * 10), "1.0 equals 1.0 + 2^(-52)."); AssertEx.That(() => !1.0.AlmostEqual(1.0 + _doublePrecision * 100), "1.0 does not equal 1.0 + 2^(-51)."); AssertEx.That(() => !1.0.AlmostEqual(2.0), "1.0 does not equal 2.0"); } + [Test] + public void AlmostEqualWithRelativeError() + { + // compare zero and negative zero + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(0.0, -0.0, 1e-5)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(0.0, -0.0, 1e-15)); + + // compare two nearby numbers + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0, 1.0 + 3 * _doublePrecision, 1e-15)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0, 1.0 + _doublePrecision, 1e-15)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0, 1.0 + 1e-16, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, 1.0 + 1e-15, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, 1.0 + 1e-14, 1e-15)); + + // compare with the two numbers reversed in compare order + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0 + 3 * _doublePrecision, 1.0, 1e-15)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0 + _doublePrecision, 1.0, 1e-15)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0 + 1e-16, 1.0, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0 + 1e-15, 1.0, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0 + 1e-14, 1.0, 1e-15)); + + // compare different numbers + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(2.0, 1.0, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, 2.0, 1e-15)); + + // compare different numbers with large tolerance + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(2.0, 1.0, 1e-5)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, 2.0, 1e-5)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(2.0, 1.0, 1e+1)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(1.0, 2.0, 1e+1)); + + // compare inf & inf + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(double.PositiveInfinity, double.PositiveInfinity, 1e-15)); + Assert.IsTrue(Precision.AlmostEqualWithRelativeError(double.NegativeInfinity, double.NegativeInfinity, 1e-15)); + + // compare -inf and inf + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(double.PositiveInfinity, double.NegativeInfinity, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(double.NegativeInfinity, double.PositiveInfinity, 1e-15)); + + // compare inf and non-inf + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(double.PositiveInfinity, 1.0, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, double.PositiveInfinity, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(double.NegativeInfinity, 1.0, 1e-15)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1.0, double.NegativeInfinity, 1e-15)); + + // compare tiny numbers with opposite signs + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(1e-12, -1e-12, 1e-14)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(-1e-12, 1e-12, 1e-14)); + + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(-2.0, 2.0, 1e-14)); + Assert.IsFalse(Precision.AlmostEqualWithRelativeError(2.0, -2.0, 1e-14)); + } + [Test] public void AlmostEqualWithMaxNumbersBetween() { diff --git a/src/Managed/Precision.cs b/src/Managed/Precision.cs index 32c46264..2b02029c 100644 --- a/src/Managed/Precision.cs +++ b/src/Managed/Precision.cs @@ -707,12 +707,61 @@ namespace MathNet.Numerics /// true if the two values differ by no more than 10 * 2^(-52); false otherwise. public static bool AlmostEqual(this double a, double b) { - if ((a == 0 && Math.Abs(b) < _defaultRelativeAccuracy) || (b == 0 && Math.Abs(a) < _defaultRelativeAccuracy)) + return AlmostEqualWithRelativeError(a, b, a - b, _defaultRelativeAccuracy); + } + + /// + /// Compares two doubles and determines if they are equal within + /// the specified maximum relative error. + /// + /// The first value. + /// The second value. + /// The relative accuracy required for being almost equal. + /// + /// if both doubles are almost equal up to the + /// specified maximum relative error, otherwise. + /// + public static bool AlmostEqualWithRelativeError(this double a, double b, double maximumRelativeError) + { + return AlmostEqualWithRelativeError(a, b, a - b, maximumRelativeError); + } + + /// + /// Compares two doubles and determines if they are equal within + /// the specified maximum relative error. + /// + /// The first value. + /// The second value. + /// The difference of the two values (according to some norm). + /// The relative accuracy required for being almost equal. + /// + /// if both doubles are almost equal up to the + /// specified maximum relative error, otherwise. + /// + public static bool AlmostEqualWithRelativeError(this double a, double b, double diff, double maximumRelativeError) + { + // If A or B are infinity (positive or negative) then + // only return true if they are exactly equal to each other - + // that is, if they are both infinities of the same sign. + if (double.IsInfinity(a) || double.IsInfinity(b)) + { + return a == b; + } + + // If A or B are a NAN, return false. NANs are equal to nothing, + // not even themselves. + if (double.IsNaN(a) || double.IsNaN(b)) + { + return false; + } + + if ((a == 0 && Math.Abs(b) < maximumRelativeError) + || (b == 0 && Math.Abs(a) < maximumRelativeError)) { return true; } - return Math.Abs(a - b) < _defaultRelativeAccuracy * Math.Max(Math.Abs(a), Math.Abs(b)); + return Math.Abs(diff) < maximumRelativeError * Math.Max(Math.Abs(a), Math.Abs(b)); } ///