Class: Whitestone::Assertion::FloatEqual

Inherits:
Base show all
Defined in:
lib/whitestone/assertion_classes.rb

Overview

class Assertion::KindOf

Constant Summary collapse

EPSILON =

Experiments have shown this value to be a reliable threshold for ratio-based comparison, but that may be machine-dependant and may be overturned by more experiments. With this epsilon, floats appear to be considered equal if their first 13 significant figures are the same.

Example: 1.1 - 1.0 gives 0.10000000000000009, which differs from 0.1 in the 17th digit. Therefore, I could, and perhaps should, be more aggressive and set an even smaller ratio like 1e-16. But I assume that complicated calculations involving floats would compound representation errors, so at the moment I choose to be conservative.

It is by design that the programmer cannot specify a value for epsilon. This should “just work”, and if someone finds a case where this epsilon is not sufficient for normal use, it is probably a bug in this library that needs to be addressed. If someone wants to experiment with different epsilon values, then they can do, for example

WhiteStone::Assertion::FloatEqual.const_set :EPSILON, 1e-16
1e-13

Instance Method Summary collapse

Methods inherited from Base

#block

Methods included from Guards

#args_or_block_one_only, #block_required, #no_block_allowed, #one_argument, #two_arguments, #two_or_three_arguments, #type_check

Constructor Details

#initialize(mode, *args, &block) ⇒ FloatEqual

Returns a new instance of FloatEqual.



284
285
286
287
288
289
290
# File 'lib/whitestone/assertion_classes.rb', line 284

def initialize(mode, *args, &block)
  super
  no_block_allowed
  type_check(args, Numeric)
  @actual, @expected = two_arguments(args).map { |x| x.to_f }
  @epsilon = EPSILON
end

Instance Method Details

#floats_essentially_equal?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


308
309
310
311
# File 'lib/whitestone/assertion_classes.rb', line 308

def floats_essentially_equal?(a, b)
  @ratio = (a/b - 1).abs
  @ratio < EPSILON
end

#messageObject



312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/whitestone/assertion_classes.rb', line 312

def message
  String.new.tap { |str|
    case @mode
    when :assert
      str << Col["Float equality test failed"].yb
      str << "\n" << Col["  Should be: #{@expected.inspect}"].gb
      str << "\n" << Col["        Was: #{@actual.inspect}"].rb
      str << "\n" <<     "    Epsilon: #{EPSILON}"
      if @ratio
        str << "\n" <<   "      Ratio: #{@ratio}"
      end
    when :negate
      line = "Float inequality test failed: the two values were essentially equal."
      str << Col[line].yb
      str << "\n" << Col["    Value 1: ", @actual.inspect  ].fmt(:yb, :rb)
      str << "\n" << Col["    Value 2: ", @expected.inspect].fmt(:yb, :rb)
      str << "\n" <<     "    Epsilon: #{EPSILON}"
      if @ratio
        str << "\n" <<   "      Ratio: #{@ratio}"
      end
    end
  }
end

#runObject



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/whitestone/assertion_classes.rb', line 291

def run
  if @actual.zero? and @expected.zero?
    true
  elsif @actual.zero? or @expected.zero?
    # Precisely one of our values is zero. Ratios don't work in this case,
    # so we work around by adding 0.01 to both to get them away from zero.
    # We check first to be sure that this would actually work.
    if (@actual - @expected).abs < 0.00001
      floats_essentially_equal?(@actual + 0.01, @expected + 0.01)
    else
      # They differ by more than 0.00001 so they're clearly not equal enough.
      false
    end
  else
    floats_essentially_equal?(@actual, @expected)
  end
end