<PackageReference Include="NUnit" Version="4.3.0" />

Tolerance

public sealed class Tolerance
The Tolerance class generalizes the notion of a tolerance within which an equality test succeeds. Normally, it is used with numeric types, but it can be used with any type that supports taking a difference between two objects and comparing that difference to a value.
using System; using System.Runtime.CompilerServices; namespace NUnit.Framework.Constraints { [Serializable] [NullableContext(1)] [Nullable(0)] public sealed class Tolerance { [Nullable(0)] public readonly struct Range { public readonly object LowerBound; public readonly object UpperBound; public Range(object lowerBound, object upperBound) { LowerBound = lowerBound; UpperBound = upperBound; } } private const string ModeMustFollowTolerance = "Tolerance amount must be specified before setting mode"; private const string MultipleToleranceModes = "Tried to use multiple tolerance modes at the same time"; private const string NumericToleranceRequired = "A numeric tolerance is required"; private const string NoNegativeTolerance = "Tolerance amount must not be negative"; public static readonly Tolerance Default = new Tolerance(0, ToleranceMode.Unset); public static readonly Tolerance Exact = new Tolerance(0, ToleranceMode.Linear); public Tolerance Percent { get { CheckLinearAndNumeric(); return new Tolerance(Amount, ToleranceMode.Percent); } } public Tolerance Ulps { get { CheckLinearAndNumeric(); return new Tolerance(Amount, ToleranceMode.Ulps); } } public Tolerance Days { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromDays(Convert.ToDouble(Amount))); } } public Tolerance Hours { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromHours(Convert.ToDouble(Amount))); } } public Tolerance Minutes { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromMinutes(Convert.ToDouble(Amount))); } } public Tolerance Seconds { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromSeconds(Convert.ToDouble(Amount))); } } public Tolerance Milliseconds { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromMilliseconds(Convert.ToDouble(Amount))); } } public Tolerance Ticks { get { CheckLinearAndNumeric(); return new Tolerance(TimeSpan.FromTicks(Convert.ToInt64(Amount))); } } public ToleranceMode Mode { get; } public object Amount { get; } public bool IsUnsetOrDefault => Mode == ToleranceMode.Unset; public bool HasVariance { get { if (Mode != 0 && this != Exact) return this != Default; return false; } } public Tolerance(object amount) : this(amount, ToleranceMode.Linear) { } private Tolerance(object amount, ToleranceMode mode) { CheckNotNegative(amount); Amount = amount; Mode = mode; } public Range ApplyToValue(object value) { switch (Mode) { default: return new Range(value, value); case ToleranceMode.Linear: return LinearRange(value); case ToleranceMode.Percent: return PercentRange(value); } } private static void CheckNotNegative(object value) { if (Numerics.IsNumericType(value) && Numerics.Compare(value, 0) < 0) throw new ArgumentException("Tolerance amount must not be negative"); if (value is TimeSpan && ((TimeSpan)value).Ticks < 0) throw new ArgumentException("Tolerance amount must not be negative"); } private void CheckLinearAndNumeric() { if (Mode != ToleranceMode.Linear) throw new InvalidOperationException((Mode == ToleranceMode.Unset) ? "Tolerance amount must be specified before setting mode" : "Tried to use multiple tolerance modes at the same time"); if (!Numerics.IsNumericType(Amount)) throw new InvalidOperationException("A numeric tolerance is required"); } private Range LinearRange(object value) { if (Amount is double || value is double) { double num = Convert.ToDouble(Amount); double num2 = Convert.ToDouble(value); return new Range(num2 - num, num2 + num); } if (Amount is float || value is float) { float num3 = Convert.ToSingle(Amount); float num4 = Convert.ToSingle(value); return new Range(num4 - num3, num4 + num3); } if (Amount is decimal || value is decimal) { decimal d = Convert.ToDecimal(Amount); decimal d2 = Convert.ToDecimal(value); return new Range(d2 - d, d2 + d); } if (Amount is ulong || value is ulong) { ulong num5 = Convert.ToUInt64(Amount); ulong num6 = Convert.ToUInt64(value); return new Range(num6 - num5, num6 + num5); } if (Amount is long || value is long) { long num7 = Convert.ToInt64(Amount); long num8 = Convert.ToInt64(value); return new Range(num8 - num7, num8 + num7); } if (Amount is uint || value is uint) { uint num9 = Convert.ToUInt32(Amount); uint num10 = Convert.ToUInt32(value); return new Range(num10 - num9, num10 + num9); } if (Numerics.IsFixedPointNumeric(Amount) && Numerics.IsFixedPointNumeric(value)) { int num11 = Convert.ToInt32(Amount); int num12 = Convert.ToInt32(value); return new Range(num12 - num11, num12 + num11); } object amount = Amount; if (amount is TimeSpan) { TimeSpan t = (TimeSpan)amount; if (value is DateTime) { DateTime d3 = (DateTime)value; return new Range(d3 - t, d3 + t); } } throw new InvalidOperationException("Cannot create range for a non-numeric value"); } private Range PercentRange(object value) { if (!Numerics.IsNumericType(Amount) || !Numerics.IsNumericType(value)) throw new InvalidOperationException("Cannot create range for a non-numeric value"); double num = Convert.ToDouble(value); double num2 = num * Convert.ToDouble(Amount) / 100; return new Range(num - num2, num + num2); } [NullableContext(2)] public override string ToString() { if (Amount != Exact.Amount || Mode != Exact.Mode) { switch (Mode) { case ToleranceMode.Unset: return "Unset"; case ToleranceMode.Linear: return Amount.ToString(); case ToleranceMode.Percent: return Amount.ToString() + " Percent"; case ToleranceMode.Ulps: return Amount.ToString() + " Ulps"; default: return "Unknown"; } } return "Exact"; } } }