1 // ***********************************************************************
2 // Copyright (c) 2008 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
31 namespace NUnit.Framework.Constraints
34 /// The Numerics class contains common operations on numeric _values.
38 #region Numeric Type Recognition
40 /// Checks the type of the object, returning true if
41 /// the object is a numeric type.
43 /// <param name="obj">The object to check</param>
44 /// <returns>true if the object is a numeric type</returns>
45 public static bool IsNumericType(Object obj)
47 return IsFloatingPointNumeric(obj) || IsFixedPointNumeric(obj);
51 /// Checks the type of the object, returning true if
52 /// the object is a floating point numeric type.
54 /// <param name="obj">The object to check</param>
55 /// <returns>true if the object is a floating point numeric type</returns>
56 public static bool IsFloatingPointNumeric(Object obj)
60 if (obj is System.Double) return true;
61 if (obj is System.Single) return true;
66 /// Checks the type of the object, returning true if
67 /// the object is a fixed point numeric type.
69 /// <param name="obj">The object to check</param>
70 /// <returns>true if the object is a fixed point numeric type</returns>
71 public static bool IsFixedPointNumeric(Object obj)
75 if (obj is System.Byte) return true;
76 if (obj is System.SByte) return true;
77 if (obj is System.Decimal) return true;
78 if (obj is System.Int32) return true;
79 if (obj is System.UInt32) return true;
80 if (obj is System.Int64) return true;
81 if (obj is System.UInt64) return true;
82 if (obj is System.Int16) return true;
83 if (obj is System.UInt16) return true;
84 if (obj is System.Char) return true;
90 #region Numeric Equality
92 /// Test two numeric _values for equality, performing the usual numeric
93 /// conversions and using a provided or default tolerance. If the tolerance
94 /// provided is Empty, this method may set it to a default tolerance.
96 /// <param name="expected">The expected value</param>
97 /// <param name="actual">The actual value</param>
98 /// <param name="tolerance">A reference to the tolerance in effect</param>
99 /// <returns>True if the _values are equal</returns>
100 public static bool AreEqual(object expected, object actual, ref Tolerance tolerance)
102 if (expected is double || actual is double)
103 return AreEqual(Convert.ToDouble(expected), Convert.ToDouble(actual), ref tolerance);
105 if (expected is float || actual is float)
106 return AreEqual(Convert.ToSingle(expected), Convert.ToSingle(actual), ref tolerance);
108 if (tolerance.Mode == ToleranceMode.Ulps)
109 throw new InvalidOperationException("Ulps may only be specified for floating point arguments");
111 if (expected is decimal || actual is decimal)
112 return AreEqual(Convert.ToDecimal(expected), Convert.ToDecimal(actual), tolerance);
114 if (expected is ulong || actual is ulong)
115 return AreEqual(Convert.ToUInt64(expected), Convert.ToUInt64(actual), tolerance);
117 if (expected is long || actual is long)
118 return AreEqual(Convert.ToInt64(expected), Convert.ToInt64(actual), tolerance);
120 if (expected is uint || actual is uint)
121 return AreEqual(Convert.ToUInt32(expected), Convert.ToUInt32(actual), tolerance);
123 return AreEqual(Convert.ToInt32(expected), Convert.ToInt32(actual), tolerance);
126 private static bool AreEqual(double expected, double actual, ref Tolerance tolerance)
128 if (double.IsNaN(expected) && double.IsNaN(actual))
131 // Handle infinity specially since subtracting two infinite _values gives
132 // NaN and the following test fails. mono also needs NaN to be handled
133 // specially although ms.net could use either method. Also, handle
134 // situation where no tolerance is used.
135 if (double.IsInfinity(expected) || double.IsNaN(expected) || double.IsNaN(actual))
137 return expected.Equals(actual);
140 if (tolerance.IsUnsetOrDefault && GlobalSettings.DefaultFloatingPointTolerance > 0.0d)
141 tolerance = new Tolerance(GlobalSettings.DefaultFloatingPointTolerance);
143 switch (tolerance.Mode)
145 case ToleranceMode.Unset:
146 return expected.Equals(actual);
148 case ToleranceMode.Linear:
149 return Math.Abs(expected - actual) <= Convert.ToDouble(tolerance.Value);
151 case ToleranceMode.Percent:
153 return expected.Equals(actual);
155 double relativeError = Math.Abs((expected - actual) / expected);
156 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
158 case ToleranceMode.Ulps:
159 return FloatingPointNumerics.AreAlmostEqualUlps(
160 expected, actual, Convert.ToInt64(tolerance.Value));
163 throw new ArgumentException("Unknown tolerance mode specified", "mode");
167 private static bool AreEqual(float expected, float actual, ref Tolerance tolerance)
169 if (float.IsNaN(expected) && float.IsNaN(actual))
172 // handle infinity specially since subtracting two infinite _values gives
173 // NaN and the following test fails. mono also needs NaN to be handled
174 // specially although ms.net could use either method.
175 if (float.IsInfinity(expected) || float.IsNaN(expected) || float.IsNaN(actual))
177 return expected.Equals(actual);
180 if (tolerance.IsUnsetOrDefault && GlobalSettings.DefaultFloatingPointTolerance > 0.0d)
181 tolerance = new Tolerance(GlobalSettings.DefaultFloatingPointTolerance);
183 switch (tolerance.Mode)
185 case ToleranceMode.Unset:
186 return expected.Equals(actual);
188 case ToleranceMode.Linear:
189 return Math.Abs(expected - actual) <= Convert.ToDouble(tolerance.Value);
191 case ToleranceMode.Percent:
192 if (expected == 0.0f)
193 return expected.Equals(actual);
194 float relativeError = Math.Abs((expected - actual) / expected);
195 return (relativeError <= Convert.ToSingle(tolerance.Value) / 100.0f);
197 case ToleranceMode.Ulps:
198 return FloatingPointNumerics.AreAlmostEqualUlps(
199 expected, actual, Convert.ToInt32(tolerance.Value));
202 throw new ArgumentException("Unknown tolerance mode specified", "mode");
207 private static bool AreEqual(decimal expected, decimal actual, Tolerance tolerance)
209 switch (tolerance.Mode)
211 case ToleranceMode.Unset:
212 return expected.Equals(actual);
214 case ToleranceMode.Linear:
215 decimal decimalTolerance = Convert.ToDecimal(tolerance.Value);
216 if (decimalTolerance > 0m)
217 return Math.Abs(expected - actual) <= decimalTolerance;
219 return expected.Equals(actual);
221 case ToleranceMode.Percent:
223 return expected.Equals(actual);
225 double relativeError = Math.Abs(
226 (double)(expected - actual) / (double)expected);
227 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
230 throw new ArgumentException("Unknown tolerance mode specified", "mode");
234 private static bool AreEqual(ulong expected, ulong actual, Tolerance tolerance)
236 switch (tolerance.Mode)
238 case ToleranceMode.Unset:
239 return expected.Equals(actual);
241 case ToleranceMode.Linear:
242 ulong ulongTolerance = Convert.ToUInt64(tolerance.Value);
243 if (ulongTolerance > 0ul)
245 ulong diff = expected >= actual ? expected - actual : actual - expected;
246 return diff <= ulongTolerance;
249 return expected.Equals(actual);
251 case ToleranceMode.Percent:
253 return expected.Equals(actual);
255 // Can't do a simple Math.Abs() here since it's unsigned
256 ulong difference = Math.Max(expected, actual) - Math.Min(expected, actual);
257 double relativeError = Math.Abs((double)difference / (double)expected);
258 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
261 throw new ArgumentException("Unknown tolerance mode specified", "mode");
265 private static bool AreEqual(long expected, long actual, Tolerance tolerance)
267 switch (tolerance.Mode)
269 case ToleranceMode.Unset:
270 return expected.Equals(actual);
272 case ToleranceMode.Linear:
273 long longTolerance = Convert.ToInt64(tolerance.Value);
274 if (longTolerance > 0L)
275 return Math.Abs(expected - actual) <= longTolerance;
277 return expected.Equals(actual);
279 case ToleranceMode.Percent:
281 return expected.Equals(actual);
283 double relativeError = Math.Abs(
284 (double)(expected - actual) / (double)expected);
285 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
288 throw new ArgumentException("Unknown tolerance mode specified", "mode");
292 private static bool AreEqual(uint expected, uint actual, Tolerance tolerance)
294 switch (tolerance.Mode)
296 case ToleranceMode.Unset:
297 return expected.Equals(actual);
299 case ToleranceMode.Linear:
300 uint uintTolerance = Convert.ToUInt32(tolerance.Value);
301 if (uintTolerance > 0)
303 uint diff = expected >= actual ? expected - actual : actual - expected;
304 return diff <= uintTolerance;
307 return expected.Equals(actual);
309 case ToleranceMode.Percent:
311 return expected.Equals(actual);
313 // Can't do a simple Math.Abs() here since it's unsigned
314 uint difference = Math.Max(expected, actual) - Math.Min(expected, actual);
315 double relativeError = Math.Abs((double)difference / (double)expected);
316 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
319 throw new ArgumentException("Unknown tolerance mode specified", "mode");
323 private static bool AreEqual(int expected, int actual, Tolerance tolerance)
325 switch (tolerance.Mode)
327 case ToleranceMode.Unset:
328 return expected.Equals(actual);
330 case ToleranceMode.Linear:
331 int intTolerance = Convert.ToInt32(tolerance.Value);
332 if (intTolerance > 0)
333 return Math.Abs(expected - actual) <= intTolerance;
335 return expected.Equals(actual);
337 case ToleranceMode.Percent:
339 return expected.Equals(actual);
341 double relativeError = Math.Abs(
342 (double)(expected - actual) / (double)expected);
343 return (relativeError <= Convert.ToDouble(tolerance.Value) / 100.0);
346 throw new ArgumentException("Unknown tolerance mode specified", "mode");
351 #region Numeric Comparisons
353 /// Compare two numeric _values, performing the usual numeric conversions.
355 /// <param name="expected">The expected value</param>
356 /// <param name="actual">The actual value</param>
357 /// <returns>The relationship of the _values to each other</returns>
358 public static int Compare(object expected, object actual)
360 if (!IsNumericType(expected) || !IsNumericType(actual))
361 throw new ArgumentException("Both arguments must be numeric");
363 if (IsFloatingPointNumeric(expected) || IsFloatingPointNumeric(actual))
364 return Convert.ToDouble(expected).CompareTo(Convert.ToDouble(actual));
366 if (expected is decimal || actual is decimal)
367 return Convert.ToDecimal(expected).CompareTo(Convert.ToDecimal(actual));
369 if (expected is ulong || actual is ulong)
370 return Convert.ToUInt64(expected).CompareTo(Convert.ToUInt64(actual));
372 if (expected is long || actual is long)
373 return Convert.ToInt64(expected).CompareTo(Convert.ToInt64(actual));
375 if (expected is uint || actual is uint)
376 return Convert.ToUInt32(expected).CompareTo(Convert.ToUInt32(actual));
378 return Convert.ToInt32(expected).CompareTo(Convert.ToInt32(actual));