Module: Philiprehberger::Approx
- Defined in:
- lib/philiprehberger/approx.rb,
lib/philiprehberger/approx/version.rb,
lib/philiprehberger/approx/comparator.rb,
lib/philiprehberger/approx/rspec_matchers.rb
Defined Under Namespace
Modules: RSpecMatchers Classes: Comparator, Error
Constant Summary collapse
- VERSION =
'0.9.0'
Class Method Summary collapse
-
.assert_near(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Object
Assert that two values are approximately equal, raising on mismatch.
-
.assert_within(a, b, abs: nil, rel: nil) ⇒ Object
Assert that two values pass within?, raising on mismatch.
-
.between?(value, min, max, epsilon: 1e-9) ⇒ Boolean
Check if a numeric value lies within [min, max] with epsilon slack on both ends.
-
.clamp(value, target, epsilon: 1e-9, rel_tol: 0) ⇒ Numeric
Snap a value to target if approximately equal, otherwise return unchanged.
-
.diff(a, b, epsilon: Float::EPSILON) ⇒ Hash
Return a diagnostic hash showing why values do or do not match.
-
.equal?(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Boolean
Check if two values are approximately equal within epsilon.
-
.near?(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Boolean
Alias for equal? with explicit epsilon.
-
.percent_equal?(a, b, percent:) ⇒ Boolean
Check if two values are approximately equal within a percentage tolerance.
-
.relative_equal?(a, b, tolerance: 1e-6) ⇒ Boolean
Check if two values are approximately equal using relative tolerance.
-
.sign_equal?(a, b, epsilon: 1e-9) ⇒ Boolean
Check if two numeric values share the same sign.
-
.tolerance_range(value, epsilon: 1e-9) ⇒ Array<Numeric>
Return the tolerance bounds [min, max] around a value for a given epsilon.
-
.within?(a, b, abs: nil, rel: nil) ⇒ Boolean
Check if two values are approximately equal using combined tolerance.
-
.zero?(value, epsilon: 1e-9) ⇒ Boolean
Check if a numeric value is approximately zero.
Class Method Details
.assert_near(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Object
Assert that two values are approximately equal, raising on mismatch
87 88 89 90 91 |
# File 'lib/philiprehberger/approx.rb', line 87 def self.assert_near(a, b, epsilon: 1e-9, rel_tol: 0) return if equal?(a, b, epsilon: epsilon, rel_tol: rel_tol) raise Error, "expected #{a.inspect} to be near #{b.inspect} (epsilon: #{epsilon}, rel_tol: #{rel_tol})" end |
.assert_within(a, b, abs: nil, rel: nil) ⇒ Object
Assert that two values pass within?, raising on mismatch
At least one of abs: or rel: must be provided.
149 150 151 152 153 |
# File 'lib/philiprehberger/approx.rb', line 149 def self.assert_within(a, b, abs: nil, rel: nil) return if within?(a, b, abs: abs, rel: rel) raise Error, "expected #{a.inspect} to be within #{b.inspect} (abs: #{abs}, rel: #{rel})" end |
.between?(value, min, max, epsilon: 1e-9) ⇒ Boolean
Check if a numeric value lies within [min, max] with epsilon slack on both ends
109 110 111 |
# File 'lib/philiprehberger/approx.rb', line 109 def self.between?(value, min, max, epsilon: 1e-9) value.between?(min - epsilon, max + epsilon) end |
.clamp(value, target, epsilon: 1e-9, rel_tol: 0) ⇒ Numeric
Snap a value to target if approximately equal, otherwise return unchanged
Returns target when value is within epsilon of target. Useful for snapping near-values to an exact canonical value.
76 77 78 |
# File 'lib/philiprehberger/approx.rb', line 76 def self.clamp(value, target, epsilon: 1e-9, rel_tol: 0) equal?(value, target, epsilon: epsilon, rel_tol: rel_tol) ? target : value end |
.diff(a, b, epsilon: Float::EPSILON) ⇒ Hash
Return a diagnostic hash showing why values do or do not match
171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/philiprehberger/approx.rb', line 171 def self.diff(a, b, epsilon: Float::EPSILON) actual_diff = (a - b).abs.to_f allowed = epsilon.to_f ratio = allowed.zero? ? Float::INFINITY : actual_diff / allowed { match: actual_diff <= allowed, actual_diff: actual_diff, allowed: allowed, ratio: ratio } end |
.equal?(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Boolean
Check if two values are approximately equal within epsilon
When rel_tol is non-zero, values match if either tolerance passes, matching Python’s math.isclose semantics: |a - b| <= max(rel_tol * max(|a|, |b|), epsilon)
22 23 24 |
# File 'lib/philiprehberger/approx.rb', line 22 def self.equal?(a, b, epsilon: 1e-9, rel_tol: 0) compare(a, b, epsilon, rel_tol) end |
.near?(a, b, epsilon: 1e-9, rel_tol: 0) ⇒ Boolean
Alias for equal? with explicit epsilon
33 34 35 |
# File 'lib/philiprehberger/approx.rb', line 33 def self.near?(a, b, epsilon: 1e-9, rel_tol: 0) equal?(a, b, epsilon: epsilon, rel_tol: rel_tol) end |
.percent_equal?(a, b, percent:) ⇒ Boolean
Check if two values are approximately equal within a percentage tolerance
161 162 163 |
# File 'lib/philiprehberger/approx.rb', line 161 def self.percent_equal?(a, b, percent:) compare_percent(a, b, percent) end |
.relative_equal?(a, b, tolerance: 1e-6) ⇒ Boolean
Check if two values are approximately equal using relative tolerance
Relative tolerance: |a - b| / max(|a|, |b|) <= tolerance Falls back to absolute comparison when both values are zero.
46 47 48 |
# File 'lib/philiprehberger/approx.rb', line 46 def self.relative_equal?(a, b, tolerance: 1e-6) compare_relative(a, b, tolerance) end |
.sign_equal?(a, b, epsilon: 1e-9) ⇒ Boolean
Check if two numeric values share the same sign
Values with |x| <= epsilon are treated as zero, so two near-zero values are considered to share a sign regardless of their raw polarity.
131 132 133 134 135 136 137 138 |
# File 'lib/philiprehberger/approx.rb', line 131 def self.sign_equal?(a, b, epsilon: 1e-9) a_zero = a.abs <= epsilon b_zero = b.abs <= epsilon return true if a_zero && b_zero return false if a_zero || b_zero (a.positive? && b.positive?) || (a.negative? && b.negative?) end |
.tolerance_range(value, epsilon: 1e-9) ⇒ Array<Numeric>
Return the tolerance bounds [min, max] around a value for a given epsilon
118 119 120 |
# File 'lib/philiprehberger/approx.rb', line 118 def self.tolerance_range(value, epsilon: 1e-9) [value - epsilon, value + epsilon] end |
.within?(a, b, abs: nil, rel: nil) ⇒ Boolean
Check if two values are approximately equal using combined tolerance
Passes if either absolute or relative tolerance is met. At least one of abs: or rel: must be provided.
60 61 62 63 64 |
# File 'lib/philiprehberger/approx.rb', line 60 def self.within?(a, b, abs: nil, rel: nil) raise ArgumentError, 'at least one of abs: or rel: must be provided' if abs.nil? && rel.nil? compare_within(a, b, abs, rel) end |
.zero?(value, epsilon: 1e-9) ⇒ Boolean
Check if a numeric value is approximately zero
98 99 100 |
# File 'lib/philiprehberger/approx.rb', line 98 def self.zero?(value, epsilon: 1e-9) value.abs <= epsilon end |