lib/sass/value/fuzzy_math.rb



# frozen_string_literal: true

module Sass
  class Value
    # Sass's {FuzzyMath} module.
    module FuzzyMath
      PRECISION = 10

      EPSILON = 10.pow(-PRECISION - 1)

      INVERSE_EPSILON = 1 / EPSILON

      module_function

      def equals(number1, number2)
        (number1 - number2).abs < EPSILON
      end

      def less_than(number1, number2)
        number1 < number2 && !equals(number1, number2)
      end

      def less_than_or_equals(number1, number2)
        number1 < number2 || equals(number1, number2)
      end

      def greater_than(number1, number2)
        number1 > number2 && !equals(number1, number2)
      end

      def greater_than_or_equals(number1, number2)
        number1 > number2 || equals(number1, number2)
      end

      def integer?(number)
        return false unless number.finite?
        return true if number.integer?

        equals((number - 0.5).abs % 1, 0.5)
      end

      def to_i(number)
        integer?(number) ? number.round : nil
      end

      def round(number)
        if number.positive?
          less_than(number % 1, 0.5) ? number.floor : number.ceil
        else
          less_than_or_equals(number % 1, 0.5) ? number.floor : number.ceil
        end
      end

      def between(number, min, max)
        return min if equals(number, min)
        return max if equals(number, max)
        return number if number > min && number < max

        nil
      end

      def assert_between(number, min, max, name)
        result = between(number, min, max)
        return result unless result.nil?

        message = "#{number} must be between #{min} and #{max}."
        raise Sass::ScriptError, name.nil? ? message : "$#{name}: #{message}"
      end

      def hash(number)
        @hash ||= if number.finite?
                    (number * INVERSE_EPSILON).round.hash
                  else
                    number.hash
                  end
      end
    end

    private_constant :FuzzyMath
  end
end