class RuboCop::Cop::Lint::FloatComparison

Checks for the presence of precise comparison of floating point numbers.

Floating point values are inherently inaccurate, and comparing them for exact equality is almost never the desired semantics. Comparison via the ‘==/!=` operators checks floating-point value representation to be exactly the same, which is very unlikely if you perform any arithmetic operations involving precision loss.

@example

# bad
x == 0.1
x != 0.1

# good - using BigDecimal
x.to_d == 0.1.to_d

# good - comparing against zero
x == 0.0
x != 0.0

# good
(x - 0.1).abs < Float::EPSILON

# good
tolerance = 0.0001
(x - 0.1).abs < tolerance

# Or some other epsilon based type of comparison:
# https://www.embeddeduse.com/2019/08/26/qt-compare-two-floats/

Constants

EQUALITY_METHODS
FLOAT_INSTANCE_METHODS
FLOAT_RETURNING_METHODS
MSG
RESTRICT_ON_SEND

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/cop/lint/float_comparison.rb, line 44
def on_send(node)
  lhs, _method, rhs = *node
  return if literal_zero?(lhs) || literal_zero?(rhs)

  add_offense(node) if float?(lhs) || float?(rhs)
end

Private Instance Methods

check_numeric_returning_method(node) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/rubocop/cop/lint/float_comparison.rb, line 89
def check_numeric_returning_method(node)
  return false unless node.receiver

  case node.method_name
  when :angle, :arg, :phase
    Float(node.receiver.source).negative?
  when :ceil, :floor, :round, :truncate
    precision = node.first_argument
    precision&.int_type? && Integer(precision.source).positive?
  end
end
check_send(node) click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/rubocop/cop/lint/float_comparison.rb, line 73
def check_send(node)
  if node.arithmetic_operation?
    lhs, _operation, rhs = *node
    float?(lhs) || float?(rhs)
  elsif FLOAT_RETURNING_METHODS.include?(node.method_name)
    true
  elsif node.receiver&.float_type?
    if FLOAT_INSTANCE_METHODS.include?(node.method_name)
      true
    else
      check_numeric_returning_method(node)
    end
  end
end
float?(node) click to toggle source
# File lib/rubocop/cop/lint/float_comparison.rb, line 53
def float?(node)
  return false unless node

  case node.type
  when :float
    true
  when :send
    check_send(node)
  when :begin
    float?(node.children.first)
  else
    false
  end
end
literal_zero?(node) click to toggle source
# File lib/rubocop/cop/lint/float_comparison.rb, line 68
def literal_zero?(node)
  node&.numeric_type? && node.value.zero?
end