@@ -103,6 +103,66 @@ def is_between(
103103 return (self > lower_bound ) & (self < upper_bound )
104104 return (self >= lower_bound ) & (self <= upper_bound )
105105
106+ def is_close (
107+ self ,
108+ other : Self | NumericLiteral ,
109+ * ,
110+ abs_tol : float ,
111+ rel_tol : float ,
112+ nans_equal : bool ,
113+ ) -> Self :
114+ from decimal import Decimal
115+
116+ other_abs : Self | NumericLiteral
117+ other_is_nan : Self | bool
118+ other_is_inf : Self | bool
119+ other_is_not_inf : Self | bool
120+
121+ if isinstance (other , (float , int , Decimal )):
122+ from math import isinf , isnan
123+
124+ # NOTE: See https://discuss.python.org/t/inferred-type-of-function-that-calls-dunder-abs-abs/101447
125+ other_abs = other .__abs__ ()
126+ other_is_nan = isnan (other )
127+ other_is_inf = isinf (other )
128+
129+ # Define the other_is_not_inf variable to prevent triggering the following warning:
130+ # > DeprecationWarning: Bitwise inversion '~' on bool is deprecated and will be
131+ # > removed in Python 3.16.
132+ other_is_not_inf = not other_is_inf
133+
134+ else :
135+ other_abs , other_is_nan = other .abs (), other .is_nan ()
136+ other_is_not_inf = other .is_finite () | other_is_nan
137+ other_is_inf = ~ other_is_not_inf
138+
139+ rel_threshold = self .abs ().clip (lower_bound = other_abs , upper_bound = None ) * rel_tol
140+ tolerance = rel_threshold .clip (lower_bound = abs_tol , upper_bound = None )
141+
142+ self_is_nan = self .is_nan ()
143+ self_is_not_inf = self .is_finite () | self_is_nan
144+
145+ # Values are close if abs_diff <= tolerance, and both finite
146+ is_close = (
147+ ((self - other ).abs () <= tolerance ) & self_is_not_inf & other_is_not_inf
148+ )
149+
150+ # Handle infinity cases: infinities are close/equal if they have the same sign
151+ self_sign , other_sign = self > 0 , other > 0
152+ is_same_inf = (~ self_is_not_inf ) & other_is_inf & (self_sign == other_sign )
153+
154+ # Handle nan cases:
155+ # * If any value is NaN, then False (via `& ~either_nan`)
156+ # * However, if `nans_equals = True` and if _both_ values are NaN, then True
157+ either_nan = self_is_nan | other_is_nan
158+ result = (is_close | is_same_inf ) & ~ either_nan
159+
160+ if nans_equal :
161+ both_nan = self_is_nan & other_is_nan
162+ result = result | both_nan
163+
164+ return result
165+
106166 def is_duplicated (self ) -> Self :
107167 return ~ self .is_unique ()
108168
0 commit comments