11namespace Microsoft . VisualStudio . TestTools . UnitTesting ;
22
3+ /// <summary>Утилиты для создания сравнителей точности и пользовательских сравнителей/проверок равенства</summary>
34public static class Accuracy
45{
6+ /// <summary>Создаёт сравнитель чисел типа <see cref="double"/> с заданной абсолютной точностью</summary>
7+ /// <param name="eps">Допустимое отклонение (ε). Должно быть неотрицательным и не NaN</param>
8+ /// <returns>Экземпляр <see cref="IEqualityComparer{Double}"/> и <see cref="IComparer{Double}"/></returns>
9+ /// <exception cref="ArgumentOutOfRangeException">Если <paramref name="eps"/> < 0</exception>
10+ /// <exception cref="ArgumentException">Если <paramref name="eps"/> равен <see cref="double.NaN"/></exception>
511 public static IEqualityComparer < double > Eps ( double eps ) => new AccuracyComparer ( eps ) ;
12+
13+ /// <summary>Создаёт сравнитель целых чисел типа <see cref="int"/> с заданной абсолютной точностью</summary>
14+ /// <param name="eps">Допустимое отклонение (ε). Должно быть неотрицательным</param>
15+ /// <returns>Экземпляр <see cref="IEqualityComparer{Int32}"/> и <see cref="IComparer{Int32}"/></returns>
16+ /// <exception cref="ArgumentOutOfRangeException">Если <paramref name="eps"/> < 0</exception>
617 public static IEqualityComparer < int > Eps ( int eps ) => new AccuracyComparer ( eps ) ;
718
8- public static IEqualityComparer < T > Equals < T > ( Func < T , T , bool > Comparer , Func < T , int > Hasher ) => new AccuracyEqualityComparer < T > ( Comparer , Hasher ) ;
9- public static IComparer < T > Compare < T > ( Comparison < T > Comparer ) => new AccuracyComparer < T > ( Comparer ) ;
19+ /// <summary>Создаёт универсальный сравнитель равенства на основе делегатов</summary>
20+ /// <typeparam name="T">Тип сравниваемых объектов</typeparam>
21+ /// <param name="Comparer">Делегат, выполняющий проверку равенства</param>
22+ /// <param name="Hasher">Делегат, вычисляющий хеш-код</param>
23+ /// <returns><see cref="IEqualityComparer{T}"/> использующий предоставленные делегаты</returns>
24+ /// <exception cref="ArgumentNullException">Если один из делегатов равен null</exception>
25+ public static IEqualityComparer < T > Equals < T > ( Func < T , T , bool > Comparer , Func < T , int > Hasher ) =>
26+ new AccuracyEqualityComparer < T > ( Comparer ?? throw new ArgumentNullException ( nameof ( Comparer ) ) ,
27+ Hasher ?? throw new ArgumentNullException ( nameof ( Hasher ) ) ) ;
28+
29+ /// <summary>Создаёт универсальный сравнитель порядка на основе делегата <see cref="Comparison{T}"/></summary>
30+ /// <typeparam name="T">Тип сравниваемых объектов</typeparam>
31+ /// <param name="Comparer">Делегат сравнения</param>
32+ /// <returns><see cref="IComparer{T}"/> использующий предоставленный делегат</returns>
33+ /// <exception cref="ArgumentNullException">Если <paramref name="Comparer"/> равен null</exception>
34+ public static IComparer < T > Compare < T > ( Comparison < T > Comparer ) =>
35+ new AccuracyComparer < T > ( Comparer ?? throw new ArgumentNullException ( nameof ( Comparer ) ) ) ;
1036}
1137
38+ /// <summary>Сравнитель значений типов <see cref="double"/> и <see cref="int"/> с учётом допустимой абсолютной погрешности</summary>
39+ /// <remarks>
40+ /// Для чисел с плавающей точкой равенство определяется условием |x - y| ≤ ε.
41+ /// Для операций хеширования используется нормализация по шагу ε (округление к сетке).
42+ /// </remarks>
43+ /// <param name="Eps">Допустимое отклонение (ε). Должно быть неотрицательным</param>
1244public readonly struct AccuracyComparer ( double Eps ) :
1345 IEqualityComparer < double > , IComparer < double > ,
1446 IEqualityComparer < int > , IComparer < int >
1547{
48+ /// <summary>Допустимое абсолютное отклонение</summary>
1649 private double Eps { get ; init ; } = Eps switch
1750 {
1851 < 0 => throw new ArgumentOutOfRangeException ( nameof ( Eps ) , Eps , "Значение точности не должно быть меньше нуля" ) ,
1952 double . NaN => throw new ArgumentException ( "Значение точности не должно быть NaN" , nameof ( Eps ) ) ,
2053 _ => Eps
2154 } ;
2255
56+ /// <summary>Проверяет равенство двух значений <see cref="double"/> с учётом точности</summary>
2357 public bool Equals ( double x , double y ) => Math . Abs ( x - y ) <= Eps ;
2458
25- public int GetHashCode ( double x ) => x is double . NaN
26- ? x . GetHashCode ( )
59+ /// <summary>Вычисляет хеш-код для значения <see cref="double"/>, нормализуя его относительно точности</summary>
60+ /// <param name="x">Значение</param>
61+ /// <returns>Хеш-код</returns>
62+ public int GetHashCode ( double x ) => x is double . NaN
63+ ? x . GetHashCode ( )
2764 : ( Math . Round ( x * Eps ) / Eps ) . GetHashCode ( ) ;
2865
66+ /// <summary>Сравнивает два значения <see cref="double"/> с учётом допустимой погрешности</summary>
67+ /// <param name="x">Первое значение</param>
68+ /// <param name="y">Второе значение</param>
69+ /// <returns>0 если считаются равными; -1 или 1 в зависимости от знака разности</returns>
70+ /// <exception cref="InvalidOperationException">Если одно из значений равно <see cref="double.NaN"/></exception>
2971 public int Compare ( double x , double y )
3072 {
3173 var delta = x - y ;
@@ -38,15 +80,19 @@ public int Compare(double x, double y)
3880 { nameof ( y ) , y } ,
3981 }
4082 } ;
41- return Math . Abs ( delta ) <= Eps
42- ? 0
83+ return Math . Abs ( delta ) <= Eps
84+ ? 0
4385 : Math . Sign ( delta ) ;
4486 }
4587
88+ /// <summary>Проверяет равенство двух значений <see cref="int"/> с учётом допустимого отклонения</summary>
4689 public bool Equals ( int x , int y ) => Math . Abs ( x - y ) <= Eps ;
4790
91+ /// <summary>Вычисляет хеш-код для целого значения с учётом нормализации к сетке точности</summary>
4892 public int GetHashCode ( int x ) => ( Math . Round ( x * Eps ) / Eps ) . GetHashCode ( ) ;
4993
94+ /// <summary>Сравнивает два значения <see cref="int"/> с учётом допустимого отклонения</summary>
95+ /// <returns>0 если считаются равными; -1 или 1 в зависимости от знака разности</returns>
5096 public int Compare ( int x , int y )
5197 {
5298 var delta = x - y ;
@@ -56,14 +102,24 @@ public int Compare(int x, int y)
56102 }
57103}
58104
105+ /// <summary>Универсальный сравнитель равенства на основе переданных делегатов</summary>
106+ /// <typeparam name="T">Тип сравниваемых объектов</typeparam>
107+ /// <param name="Comparer">Делегат проверки равенства</param>
108+ /// <param name="Hasher">Делегат вычисления хеш-кода</param>
59109public class AccuracyEqualityComparer < T > ( Func < T , T , bool > Comparer , Func < T , int > Hasher ) : IEqualityComparer < T >
60110{
111+ /// <summary>Проверяет равенство двух объектов</summary>
61112 public bool Equals ( T x , T y ) => Comparer ( x , y ) ;
62113
114+ /// <summary>Вычисляет хеш-код объекта</summary>
63115 public int GetHashCode ( T obj ) => Hasher ( obj ) ;
64116}
65117
118+ /// <summary>Универсальный сравнитель порядка на основе делегата <see cref="Comparison{T}"/></summary>
119+ /// <typeparam name="T">Тип сравниваемых объектов</typeparam>
120+ /// <param name="Comparer">Делегат сравнения</param>
66121public class AccuracyComparer < T > ( Comparison < T > Comparer ) : IComparer < T >
67122{
123+ /// <summary>Сравнивает два значения</summary>
68124 public int Compare ( T x , T y ) => Comparer ( x , y ) ;
69125}
0 commit comments