diff --git a/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs
new file mode 100644
index 00000000..57364eba
--- /dev/null
+++ b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustBeApproximatelyBenchmark.cs
@@ -0,0 +1,25 @@
+using System;
+using BenchmarkDotNet.Attributes;
+
+namespace Light.GuardClauses.Performance.ComparableAssertions;
+
+[MemoryDiagnoser]
+// ReSharper disable once ClassCanBeSealed.Global -- Benchmark.NET derives from this class with dynamically created code
+public class MustBeApproximatelyBenchmark
+{
+ [Benchmark]
+ public double MustBeApproximately() => 5.100001.MustBeApproximately(5.100000, 0.0001);
+
+ [Benchmark(Baseline = true)]
+ public double Imperative() => Imperative(5.100001, 5.100000, 0.0001);
+
+ private static double Imperative(double parameter, double other, double tolerance)
+ {
+ if (Math.Abs(parameter - other) > tolerance)
+ {
+ throw new ArgumentOutOfRangeException(nameof(parameter), $"Value is not approximately {other}");
+ }
+
+ return parameter;
+ }
+}
diff --git a/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs
new file mode 100644
index 00000000..a65d4b84
--- /dev/null
+++ b/Code/Light.GuardClauses.Performance/ComparableAssertions/MustNotBeApproximatelyBenchmark.cs
@@ -0,0 +1,25 @@
+using System;
+using BenchmarkDotNet.Attributes;
+
+namespace Light.GuardClauses.Performance.ComparableAssertions;
+
+[MemoryDiagnoser]
+// ReSharper disable once ClassCanBeSealed.Global -- Benchmark.NET derives from this class with dynamically created code
+public class MustNotBeApproximatelyBenchmark
+{
+ [Benchmark]
+ public double MustNotBeApproximately() => 5.2.MustNotBeApproximately(5.1, 0.5);
+
+ [Benchmark(Baseline = true)]
+ public double Imperative() => Imperative(5.2, 5.1, 0.5);
+
+ private static double Imperative(double parameter, double other, double tolerance)
+ {
+ if (Math.Abs(parameter - other) < tolerance)
+ {
+ throw new ArgumentOutOfRangeException(nameof(parameter), $"Value is approximately {other}");
+ }
+
+ return parameter;
+ }
+}
\ No newline at end of file
diff --git a/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj b/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj
index 7ffe52c8..cbf14189 100644
--- a/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj
+++ b/Code/Light.GuardClauses.Performance/Light.GuardClauses.Performance.csproj
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/Code/Light.GuardClauses.Performance/Program.cs b/Code/Light.GuardClauses.Performance/Program.cs
index 6bdd8259..b17928a0 100644
--- a/Code/Light.GuardClauses.Performance/Program.cs
+++ b/Code/Light.GuardClauses.Performance/Program.cs
@@ -4,18 +4,17 @@
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
-namespace Light.GuardClauses.Performance
+namespace Light.GuardClauses.Performance;
+
+public static class Program
{
- public static class Program
- {
- private static IConfig DefaultConfiguration =>
- DefaultConfig
- .Instance
- .AddJob(Job.Default.WithRuntime(CoreRuntime.Core70))
- .AddJob(Job.Default.WithRuntime(ClrRuntime.Net48))
- .AddDiagnoser(MemoryDiagnoser.Default, new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig()));
+ private static IConfig DefaultConfiguration =>
+ DefaultConfig
+ .Instance
+ .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80))
+ .AddJob(Job.Default.WithRuntime(ClrRuntime.Net48))
+ .AddDiagnoser(MemoryDiagnoser.Default, new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig()));
- public static void Main(string[] arguments) =>
- BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(arguments, DefaultConfiguration);
- }
+ public static void Main(string[] arguments) =>
+ BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(arguments, DefaultConfiguration);
}
\ No newline at end of file
diff --git a/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs
index c0abf2d3..afbf5af1 100644
--- a/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs
+++ b/Code/Light.GuardClauses.Tests/CommonAssertions/IsApproximatelyTests.cs
@@ -38,4 +38,60 @@ public static void FloatWithDefaultTolerance(float first, float second, bool exp
[InlineData(5.0f, 15.0001f, 10.0f, false)]
public static void FloatWithCustomTolerance(float first, float second, float tolerance, bool expected) =>
first.IsApproximately(second, tolerance).Should().Be(expected);
-}
\ No newline at end of file
+
+#if NET8_0
+ [Theory]
+ [InlineData(1.1, 1.3, 0.5, true)]
+ [InlineData(100.55, 100.555, 0.00001, false)]
+ [InlineData(5.0, 14.999999, 10.0, true)]
+ [InlineData(5.0, 15.0, 10.0, false)]
+ [InlineData(5.0, 15.0001, 10.0, false)]
+ public static void GenericDoubleWithCustomTolerance(double first, double second, double tolerance, bool expected) =>
+ first.IsApproximately(second, tolerance).Should().Be(expected);
+
+ [Theory]
+ [InlineData(1.1f, 1.3f, 0.5f, true)]
+ [InlineData(100.55f, 100.555f, 0.00001f, false)]
+ [InlineData(5.0f, 14.999999f, 10.0f, true)]
+ [InlineData(5.0f, 15.0f, 10.0f, false)]
+ [InlineData(5.0f, 15.0001f, 10.0f, false)]
+ public static void GenericFloatWithCustomTolerance(float first, float second, float tolerance, bool expected) =>
+ first.IsApproximately(second, tolerance).Should().Be(expected);
+
+ [Theory]
+ [InlineData(5, 10, 10, true)]
+ [InlineData(5, 15, 10, false)]
+ [InlineData(-5, 5, 12, true)]
+ [InlineData(-100, 100, 199, false)]
+ [InlineData(42, 42, 1, true)]
+ public static void GenericIntWithCustomTolerance(int first, int second, int tolerance, bool expected) =>
+ first.IsApproximately(second, tolerance).Should().Be(expected);
+
+ [Theory]
+ [InlineData(5L, 10L, 10L, true)]
+ [InlineData(5L, 15L, 10L, false)]
+ [InlineData(-5L, 5L, 12L, true)]
+ [InlineData(-100L, 100L, 199L, false)]
+ [InlineData(42L, 42L, 1L, true)]
+ public static void GenericLongWithCustomTolerance(long first, long second, long tolerance, bool expected) =>
+ first.IsApproximately(second, tolerance).Should().Be(expected);
+
+ [Theory]
+ [MemberData(nameof(DecimalTestData))]
+ public static void GenericDecimalWithCustomTolerance(
+ decimal first,
+ decimal second,
+ decimal tolerance,
+ bool expected
+ ) =>
+ first.IsApproximately(second, tolerance).Should().Be(expected);
+
+ public static TheoryData DecimalTestData() => new ()
+ {
+ { 1.1m, 1.3m, 0.5m, true },
+ { 100.55m, 100.555m, 0.00001m, false },
+ { 5.0m, 14.999999m, 10.0m, true },
+ { 5.0m, 15.0m, 10.0m, false },
+ };
+#endif
+}
diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs
new file mode 100644
index 00000000..32046559
--- /dev/null
+++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustBeApproximatelyTests.cs
@@ -0,0 +1,236 @@
+using System;
+using FluentAssertions;
+using Xunit;
+
+namespace Light.GuardClauses.Tests.ComparableAssertions;
+
+public static class MustBeApproximatelyTests
+{
+ [Theory]
+ [InlineData(5.1, 5.0, 0.2)]
+ [InlineData(10.3, 10.3, 0.01)]
+ [InlineData(3.14159, 3.14, 0.002)]
+ [InlineData(-42.0, -42.0001, 0.001)]
+ public static void ValuesApproximatelyEqual_Double(double value, double other, double tolerance) =>
+ value.MustBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.1f, 5.0f, 0.2f)]
+ [InlineData(10.3f, 10.3f, 0.01f)]
+ [InlineData(3.14159f, 3.14f, 0.002f)]
+ [InlineData(-42.0f, -42.0001f, 0.001f)]
+ public static void ValuesApproximatelyEqual_Float(float value, float other, float tolerance) =>
+ value.MustBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.0, 5.3, 0.1)]
+ [InlineData(100.0, 99.8, 0.1)]
+ [InlineData(-20.0, -20.2, 0.1)]
+ [InlineData(0.0001, 0.0002, 0.00005)]
+ public static void ValuesNotApproximatelyEqual_Double(double value, double other, double tolerance)
+ {
+ var act = () => value.MustBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Theory]
+ [InlineData(5.0f, 5.3f, 0.1f)]
+ [InlineData(100.0f, 99.8f, 0.1f)]
+ [InlineData(-20.0f, -20.2f, 0.1f)]
+ [InlineData(0.0001f, 0.0002f, 0.00005f)]
+ public static void ValuesNotApproximatelyEqual_Float(float value, float other, float tolerance)
+ {
+ var act = () => value.MustBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Fact]
+ public static void DefaultTolerance_Double()
+ {
+ // Should pass - difference is 0.00005 which is less than default tolerance 0.0001
+ const double value = 1.00005;
+ value.MustBeApproximately(1.0).Should().Be(value);
+
+ // Should throw - difference is 0.0002 which is greater than default tolerance 0.0001
+ Action act = () => 1.0002.MustBeApproximately(1.0, "parameter");
+ act.Should().Throw()
+ .WithParameterName("parameter");
+ }
+
+ [Fact]
+ public static void DefaultTolerance_Float()
+ {
+ // Should pass - difference is 0.00005f which is less than default tolerance 0.0001f
+ const float value = 1.00005f;
+ value.MustBeApproximately(1.0f).Should().Be(value);
+
+ // Should throw - difference is 0.0002f which is greater than default tolerance 0.0001f
+ Action act = () => 1.0002f.MustBeApproximately(1.0f, "parameter");
+ act.Should().Throw()
+ .WithParameterName("parameter");
+ }
+
+ [Fact]
+ public static void CustomException_Double() =>
+ Test.CustomException(
+ 5.0,
+ 5.3,
+ (x, y, exceptionFactory) => x.MustBeApproximately(y, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Double() =>
+ Test.CustomException(
+ 5.0,
+ 5.5,
+ 0.1,
+ (x, y, z, exceptionFactory) => x.MustBeApproximately(y, z, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomException_Float() =>
+ Test.CustomException(
+ 5.0f,
+ 5.3f,
+ (x, y, exceptionFactory) => x.MustBeApproximately(y, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Float() =>
+ Test.CustomException(
+ 5.0f,
+ 5.5f,
+ 0.1f,
+ (x, y, z, exceptionFactory) => x.MustBeApproximately(y, z, exceptionFactory)
+ );
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Double() =>
+ 5.0.MustBeApproximately(5.05, 0.1, (_, _, _) => null).Should().Be(5.0);
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Float() =>
+ 5.0f.MustBeApproximately(5.05f, 0.1f, (_, _, _) => null).Should().Be(5.0f);
+
+ [Fact]
+ public static void CustomMessage_Double() =>
+ Test.CustomMessage(
+ message => 100.0.MustBeApproximately(101.0, 0.5, message: message)
+ );
+
+ [Fact]
+ public static void CustomMessage_Float() =>
+ Test.CustomMessage(
+ message => 100.0f.MustBeApproximately(101.0f, 0.5f, message: message)
+ );
+
+ [Fact]
+ public static void CallerArgumentExpression_Double()
+ {
+ const double seventyEightO1 = 78.1;
+
+ var act = () => seventyEightO1.MustBeApproximately(3.0);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Double()
+ {
+ const double pi = 3.14159;
+
+ var act = () => pi.MustBeApproximately(3.0, 0.1);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(pi));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpression_Float()
+ {
+ const float seventyEightO1 = 78.1f;
+
+ var act = () => seventyEightO1.MustBeApproximately(3.0f);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Float()
+ {
+ const float pi = 3.14159f;
+
+ var act = () => pi.MustBeApproximately(3.0f, 0.1f);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(pi));
+ }
+
+#if NET8_0
+ [Theory]
+ [InlineData(5.1, 5.0, 0.2)]
+ [InlineData(10.3, 10.3, 0.01)]
+ [InlineData(3.14159, 3.14, 0.002)]
+ [InlineData(-42.0, -42.0001, 0.001)]
+ public static void ValuesApproximatelyEqual_Generic(double value, double other, double tolerance) =>
+ value.MustBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.0, 5.3, 0.1)]
+ [InlineData(100.0, 99.8, 0.1)]
+ [InlineData(-20.0, -20.2, 0.1)]
+ [InlineData(0.0001, 0.0002, 0.00005)]
+ public static void ValuesNotApproximatelyEqual_Generic(double value, double other, double tolerance)
+ {
+ var act = () => value.MustBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Generic() =>
+ Test.CustomException(
+ 5.0,
+ 5.3,
+ 0.2,
+ (x, y, t, exceptionFactory) => x.MustBeApproximately(y, t, exceptionFactory)
+ );
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Generic() =>
+ 5.0.MustBeApproximately(5.05, 0.1, (_, _, _) => null).Should().Be(5.0);
+
+ [Fact]
+ public static void CustomMessage_Generic() =>
+ Test.CustomMessage(
+ message => 100.0.MustBeApproximately(101.0, 0.5, message: message)
+ );
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Generic()
+ {
+ const double seventyEightO1 = 78.1;
+
+ var act = () => seventyEightO1.MustBeApproximately(3.0, 10.0);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+#endif
+}
diff --git a/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs
new file mode 100644
index 00000000..da9e7ea6
--- /dev/null
+++ b/Code/Light.GuardClauses.Tests/ComparableAssertions/MustNotBeApproximatelyTests.cs
@@ -0,0 +1,238 @@
+using System;
+using FluentAssertions;
+using Xunit;
+
+namespace Light.GuardClauses.Tests.ComparableAssertions;
+
+public static class MustNotBeApproximatelyTests
+{
+ [Theory]
+ [InlineData(5.3, 5.0, 0.2)]
+ [InlineData(10.4, 10.3, 0.01)]
+ [InlineData(3.15, 3.14, 0.001)]
+ [InlineData(-42.002, -42.0001, 0.001)]
+ public static void ValuesNotApproximatelyEqual_Double(double value, double other, double tolerance) =>
+ value.MustNotBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.3f, 5.0f, 0.2f)]
+ [InlineData(10.4f, 10.3f, 0.01f)]
+ [InlineData(3.15f, 3.14f, 0.001f)]
+ [InlineData(-42.002f, -42.0001f, 0.001f)]
+ public static void ValuesNotApproximatelyEqual_Float(float value, float other, float tolerance) =>
+ value.MustNotBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.0, 5.05, 0.1)]
+ [InlineData(100.0, 99.95, 0.1)]
+ [InlineData(-20.0, -20.05, 0.1)]
+ [InlineData(0.0001, 0.00015, 0.0001)]
+ public static void ValuesApproximatelyEqual_Double(double value, double other, double tolerance)
+ {
+ var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Theory]
+ [InlineData(5.0f, 5.05f, 0.1f)]
+ [InlineData(100.0f, 99.95f, 0.1f)]
+ [InlineData(-20.0f, -20.05f, 0.1f)]
+ [InlineData(0.0001f, 0.00015f, 0.0001f)]
+ public static void ValuesApproximatelyEqual_Float(float value, float other, float tolerance)
+ {
+ var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Fact]
+ public static void DefaultTolerance_Double()
+ {
+ // Should throw - difference is 0.00005 which is less than default tolerance 0.0001
+ const double value = 1.00005;
+ Action act = () => value.MustNotBeApproximately(1.0, "parameter");
+ act.Should().Throw()
+ .WithParameterName("parameter");
+
+ // Should pass - difference is 0.0002 which is greater than default tolerance 0.0001
+ const double value2 = 1.0002;
+ value2.MustNotBeApproximately(1.0).Should().Be(value2);
+ }
+
+ [Fact]
+ public static void DefaultTolerance_Float()
+ {
+ // Should throw - difference is 0.00005f which is less than default tolerance 0.0001f
+ const float value = 1.00005f;
+ Action act = () => value.MustNotBeApproximately(1.0f, "parameter");
+ act.Should().Throw()
+ .WithParameterName("parameter");
+
+ // Should pass - difference is 0.0002f which is greater than default tolerance 0.0001f
+ const float value2 = 1.0002f;
+ value2.MustNotBeApproximately(1.0f).Should().Be(value2);
+ }
+
+ [Fact]
+ public static void CustomException_Double() =>
+ Test.CustomException(
+ 5.0,
+ 5.0000000001,
+ (x, y, exceptionFactory) => x.MustNotBeApproximately(y, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Double() =>
+ Test.CustomException(
+ 5.0,
+ 5.05,
+ 0.1,
+ (x, y, z, exceptionFactory) => x.MustNotBeApproximately(y, z, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomException_Float() =>
+ Test.CustomException(
+ 5.0f,
+ 5.000001f,
+ (x, y, exceptionFactory) => x.MustNotBeApproximately(y, exceptionFactory)
+ );
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Float() =>
+ Test.CustomException(
+ 5.0f,
+ 5.05f,
+ 0.1f,
+ (x, y, z, exceptionFactory) => x.MustNotBeApproximately(y, z, exceptionFactory)
+ );
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Double() =>
+ 5.2.MustNotBeApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2);
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Float() =>
+ 5.2f.MustNotBeApproximately(5.0f, 0.1f, (_, _, _) => null).Should().Be(5.2f);
+
+ [Fact]
+ public static void CustomMessage_Double() =>
+ Test.CustomMessage(
+ message => 100.0.MustNotBeApproximately(100.05, 0.1, message: message)
+ );
+
+ [Fact]
+ public static void CustomMessage_Float() =>
+ Test.CustomMessage(
+ message => 100.0f.MustNotBeApproximately(100.05f, 0.1f, message: message)
+ );
+
+ [Fact]
+ public static void CallerArgumentExpression_Double()
+ {
+ const double seventyEightO1 = 78.1;
+
+ var act = () => seventyEightO1.MustNotBeApproximately(78.099999);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Double()
+ {
+ const double pi = 3.14159;
+
+ var act = () => pi.MustNotBeApproximately(3.14, 0.01);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(pi));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpression_Float()
+ {
+ const float seventyEightO1 = 78.1f;
+
+ var act = () => seventyEightO1.MustNotBeApproximately(78.100005f);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Float()
+ {
+ const float pi = 3.14159f;
+
+ var act = () => pi.MustNotBeApproximately(3.14f, 0.01f);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(pi));
+ }
+
+#if NET8_0
+ [Theory]
+ [InlineData(5.3, 5.0, 0.2)]
+ [InlineData(10.4, 10.3, 0.01)]
+ [InlineData(3.15, 3.14, 0.001)]
+ [InlineData(-42.002, -42.0001, 0.001)]
+ public static void ValuesNotApproximatelyEqual_Generic(double value, double other, double tolerance) =>
+ value.MustNotBeApproximately(other, tolerance).Should().Be(value);
+
+ [Theory]
+ [InlineData(5.0, 5.05, 0.1)]
+ [InlineData(100.0, 99.95, 0.1)]
+ [InlineData(-20.0, -20.05, 0.1)]
+ [InlineData(0.0001, 0.00015, 0.0001)]
+ public static void ValuesApproximatelyEqual_Generic(double value, double other, double tolerance)
+ {
+ var act = () => value.MustNotBeApproximately(other, tolerance, nameof(value));
+
+ var exceptionAssertion = act.Should().Throw().Which;
+ exceptionAssertion.Message.Should().Contain(
+ $"{nameof(value)} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {value}."
+ );
+ exceptionAssertion.ParamName.Should().BeSameAs(nameof(value));
+ }
+
+ [Fact]
+ public static void CustomExceptionWithTolerance_Generic() =>
+ Test.CustomException(
+ 5.0,
+ 5.05,
+ 0.1,
+ (x, y, t, exceptionFactory) => x.MustNotBeApproximately(y, t, exceptionFactory)
+ );
+
+ [Fact]
+ public static void NoCustomExceptionThrown_Generic() =>
+ 5.2.MustNotBeApproximately(5.0, 0.1, (_, _, _) => null).Should().Be(5.2);
+
+ [Fact]
+ public static void CustomMessage_Generic() =>
+ Test.CustomMessage(
+ message => 100.0.MustNotBeApproximately(100.05, 0.1, message: message)
+ );
+
+ [Fact]
+ public static void CallerArgumentExpressionWithTolerance_Generic()
+ {
+ const double seventyEightO1 = 78.1;
+
+ var act = () => seventyEightO1.MustNotBeApproximately(78.0, 0.2);
+
+ act.Should().Throw()
+ .WithParameterName(nameof(seventyEightO1));
+ }
+#endif
+}
\ No newline at end of file
diff --git a/Code/Light.GuardClauses/Check.IsApproximately.cs b/Code/Light.GuardClauses/Check.IsApproximately.cs
index 698450ed..6ee61729 100644
--- a/Code/Light.GuardClauses/Check.IsApproximately.cs
+++ b/Code/Light.GuardClauses/Check.IsApproximately.cs
@@ -1,5 +1,8 @@
using System;
using System.Runtime.CompilerServices;
+#if NET8_0
+using System.Numerics;
+#endif
namespace Light.GuardClauses;
@@ -35,8 +38,8 @@ public static bool IsApproximately(this double value, double other) =>
///
/// Checks if the specified value is approximately the same as the other value, using the given tolerance.
///
- /// The first value to compare.
- /// The second value to compare.
+ /// The first value to be compared.
+ /// The second value to be compared.
/// The tolerance indicating how much the two values may differ from each other.
///
/// True if and are equal or if their absolute difference
@@ -58,4 +61,21 @@ public static bool IsApproximately(this float value, float other, float toleranc
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsApproximately(this float value, float other) =>
Math.Abs(value - other) < 0.0001f;
+
+#if NET8_0
+ ///
+ /// Checks if the specified value is approximately the same as the other value, using the given tolerance.
+ ///
+ /// The first value to be compared.
+ /// The second value to be compared.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The type that implements the interface.
+ ///
+ /// True if and are equal or if their absolute difference
+ /// is smaller than the given , otherwise false.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsApproximately(this T value, T other, T tolerance) where T : INumber =>
+ T.Abs(value - other) < tolerance;
+#endif
}
diff --git a/Code/Light.GuardClauses/Check.MustBeApproximately.cs b/Code/Light.GuardClauses/Check.MustBeApproximately.cs
new file mode 100644
index 00000000..77bdf05b
--- /dev/null
+++ b/Code/Light.GuardClauses/Check.MustBeApproximately.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Runtime.CompilerServices;
+using Light.GuardClauses.ExceptionFactory;
+#if NET8_0
+using System.Numerics;
+#endif
+
+namespace Light.GuardClauses;
+
+public static partial class Check
+{
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustBeApproximately(
+ this double parameter,
+ double other,
+ [CallerArgumentExpression(nameof(parameter))] string? parameterName = null,
+ string? message = null
+ ) =>
+ parameter.MustBeApproximately(other, 0.0001, parameterName, message);
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ ///
+ /// The delegate that creates your custom exception. and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustBeApproximately(
+ this double parameter,
+ double other,
+ Func exceptionFactory
+ )
+ {
+ if (!parameter.IsApproximately(other))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustBeApproximately(
+ this double parameter,
+ double other,
+ double tolerance,
+ [CallerArgumentExpression(nameof(parameter))] string? parameterName = null,
+ string? message = null
+ )
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The delegate that creates your custom exception. ,
+ /// , and are passed to this delegate.
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is not less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustBeApproximately(
+ this double parameter,
+ double other,
+ double tolerance,
+ Func exceptionFactory
+ )
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, using the default tolerance of 0.0001f, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is
+ /// not less than 0.0001f.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustBeApproximately(
+ this float parameter,
+ float other,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) =>
+ parameter.MustBeApproximately(other, 0.0001f, parameterName, message);
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ ///
+ /// The delegate that creates your custom exception. and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustBeApproximately(
+ this float parameter,
+ float other,
+ Func exceptionFactory
+ )
+ {
+ if (!parameter.IsApproximately(other))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustBeApproximately(
+ this float parameter,
+ float other,
+ float tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ )
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ ///
+ /// The delegate that creates your custom exception. , , and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is not less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustBeApproximately(
+ this float parameter,
+ float other,
+ float tolerance,
+ Func exceptionFactory
+ )
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+
+#if NET8_0
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ /// The type that implements the interface.
+ ///
+ /// Thrown when the absolute difference between and is not
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T MustBeApproximately(
+ this T parameter,
+ T other,
+ T tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) where T : INumber
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The delegate that creates your custom exception. ,
+ /// , and are passed to this delegate.
+ /// The type that implements the interface.
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is not less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T MustBeApproximately(
+ this T parameter,
+ T other,
+ T tolerance,
+ Func exceptionFactory
+ ) where T : INumber
+ {
+ if (!parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+#endif
+}
diff --git a/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs b/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs
new file mode 100644
index 00000000..909ffe01
--- /dev/null
+++ b/Code/Light.GuardClauses/Check.MustNotBeApproximately.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Runtime.CompilerServices;
+using Light.GuardClauses.ExceptionFactory;
+#if NET8_0
+using System.Numerics;
+#endif
+
+namespace Light.GuardClauses;
+
+public static partial class Check
+{
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustNotBeApproximately(
+ this double parameter,
+ double other,
+ [CallerArgumentExpression(nameof(parameter))] string? parameterName = null,
+ string? message = null
+ ) =>
+ parameter.MustNotBeApproximately(other, 0.0001, parameterName, message);
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ ///
+ /// The delegate that creates your custom exception. and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustNotBeApproximately(
+ this double parameter,
+ double other,
+ Func exceptionFactory
+ )
+ {
+ if (parameter.IsApproximately(other))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustNotBeApproximately(
+ this double parameter,
+ double other,
+ double tolerance,
+ [CallerArgumentExpression(nameof(parameter))] string? parameterName = null,
+ string? message = null
+ )
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The delegate that creates your custom exception. ,
+ /// , and are passed to this delegate.
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static double MustNotBeApproximately(
+ this double parameter,
+ double other,
+ double tolerance,
+ Func exceptionFactory
+ )
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, using the default tolerance of 0.0001f, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than 0.0001f.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustNotBeApproximately(
+ this float parameter,
+ float other,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) =>
+ parameter.MustNotBeApproximately(other, 0.0001f, parameterName, message);
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, using the default tolerance of 0.0001, or otherwise throws an
+ /// .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ ///
+ /// The delegate that creates your custom exception. and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than 0.0001.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustNotBeApproximately(
+ this float parameter,
+ float other,
+ Func exceptionFactory
+ )
+ {
+ if (parameter.IsApproximately(other))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustNotBeApproximately(
+ this float parameter,
+ float other,
+ float tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ )
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ ///
+ /// The delegate that creates your custom exception. , , and
+ /// are passed to this delegate.
+ ///
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float MustNotBeApproximately(
+ this float parameter,
+ float other,
+ float tolerance,
+ Func exceptionFactory
+ )
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+
+#if NET8_0
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws an .
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The name of the parameter (optional).
+ /// The message that will be passed to the resulting exception (optional).
+ /// The type that implements the interface.
+ ///
+ /// Thrown when the absolute difference between and is
+ /// less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T MustNotBeApproximately(
+ this T parameter,
+ T other,
+ T tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) where T : INumber
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.MustNotBeApproximately(parameter, other, tolerance, parameterName, message);
+ }
+
+ return parameter;
+ }
+
+ ///
+ /// Ensures that the specified is not approximately equal to the given
+ /// value, or otherwise throws your custom exception.
+ ///
+ /// The value to be checked.
+ /// The value that should not be approximately equal to.
+ /// The tolerance indicating how much the two values may differ from each other.
+ /// The delegate that creates your custom exception. ,
+ /// , and are passed to this delegate.
+ /// The type that implements the interface.
+ ///
+ /// Your custom exception thrown when the absolute difference between and
+ /// is less than .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T MustNotBeApproximately(
+ this T parameter,
+ T other,
+ T tolerance,
+ Func exceptionFactory
+ ) where T : INumber
+ {
+ if (parameter.IsApproximately(other, tolerance))
+ {
+ Throw.CustomException(exceptionFactory, parameter, other, tolerance);
+ }
+
+ return parameter;
+ }
+#endif
+}
\ No newline at end of file
diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs
new file mode 100644
index 00000000..e4ba742e
--- /dev/null
+++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustBeApproximately.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace Light.GuardClauses.ExceptionFactory;
+
+public static partial class Throw
+{
+ ///
+ /// Throws the default indicating that a value must be approximately
+ /// equal to another value within a specified tolerance, using the optional parameter name and message.
+ ///
+ [ContractAnnotation("=> halt")]
+ [DoesNotReturn]
+ public static void MustBeApproximately(
+ T parameter,
+ T other,
+ T tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) =>
+ throw new ArgumentOutOfRangeException(
+ parameterName,
+ message ??
+ $"{parameterName ?? "The value"} must be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."
+ );
+}
diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs
new file mode 100644
index 00000000..3b8adfab
--- /dev/null
+++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.MustNotBeApproximately.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
+
+namespace Light.GuardClauses.ExceptionFactory;
+
+public static partial class Throw
+{
+ ///
+ /// Throws the default indicating that a value must not be approximately
+ /// equal to another value within a specified tolerance, using the optional parameter name and message.
+ ///
+ [ContractAnnotation("=> halt")]
+ [DoesNotReturn]
+ public static void MustNotBeApproximately(
+ T parameter,
+ T other,
+ T tolerance,
+ [CallerArgumentExpression("parameter")] string? parameterName = null,
+ string? message = null
+ ) =>
+ throw new ArgumentOutOfRangeException(
+ parameterName,
+ message ??
+ $"{parameterName ?? "The value"} must not be approximately equal to {other} with a tolerance of {tolerance}, but it actually is {parameter}."
+ );
+}
\ No newline at end of file