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]
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public sealed class Tolerance
{
[System.Runtime.CompilerServices.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";
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)
{
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 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);
}
[System.Runtime.CompilerServices.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";
}
}
}