rubinius/rubinius

View on GitHub
core/float.rb

Summary

Maintainability
F
3 days
Test Coverage
class Float < Numeric
  def **(other)
    Rubinius.primitive :float_pow

    if other.is_a?(Float) && self < 0 && other != other.round
      return Complex.new(self, 0) ** other
    end

    b, a = math_coerce other
    a ** b
  end

  def imaginary
    0
  end

  def numerator
    if nan?
      NAN
    elsif infinite? == 1
      INFINITY
    elsif infinite? == -1
      -INFINITY
    else
      super
    end
  end

  def denominator
    if infinite? || nan?
      1
    else
      super
    end
  end

  def to_r
    f, e = Math.frexp self
    f = Math.ldexp(f, MANT_DIG).to_i
    e -= MANT_DIG

    (f * (RADIX ** e)).to_r
  end

  def arg
    if nan?
      self
    elsif signbit?
      Math::PI
    else
      0
    end
  end
  alias_method :angle, :arg
  alias_method :phase, :arg

  def rationalize(eps=undefined)
    if undefined.equal?(eps)
      f, n = Math.frexp self
      f = Math.ldexp(f, Float::MANT_DIG).to_i
      n -= Float::MANT_DIG

      Rational.new(2 * f, 1 << (1 - n)).rationalize(Rational.new(1, 1 << (1 - n)))
    else
      to_r.rationalize(eps)
    end
  end

  def round(ndigits=0)
    ndigits = Rubinius::Type.coerce_to(ndigits, Integer, :to_int)

    if ndigits == 0
      return Rubinius.invoke_primitive :float_round, self
    elsif ndigits < 0
      return truncate.round ndigits
    end

    return self if infinite? or nan?

    _, exp = Math.frexp(self)

    if ndigits >= (Float::DIG + 2) - (exp > 0 ? exp / 4 : exp / 3 - 1)
      return self
    end

    if ndigits < -(exp > 0 ? exp / 3 + 1 : exp / 4)
      return 0.0
    end

    f = 10**ndigits
    Rubinius.invoke_primitive(:float_round, self * f) / f.to_f
  end
  def coerce(other)
    return [other, self] if other.kind_of? Float
    [Float(other), self]
  end

  def -@
    Rubinius.primitive :float_neg
    raise PrimitiveFailure, "Float#-@ primitive failed"
  end

  def abs
    FFI::Platform::Math.fabs(self)
  end

  alias_method :magnitude, :abs

  def signbit?
    Rubinius.primitive :float_signbit_p
    raise PrimitiveFailure, "Float#signbit? primitive failed"
  end

  def +(other)
    Rubinius.asm do
      flt = new_label
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_1 r3
      n_ieq r3, r3, r2
      b_if r3, flt
      goto done

      flt.set!
      n_dadd r0, r0, r1
      r_store_float r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other
    a + b
  end

  def -(other)
    Rubinius.asm do
      flt = new_label
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_1 r3
      n_ieq r3, r3, r2
      b_if r3, flt
      goto done

      flt.set!
      n_dsub r0, r0, r1
      r_store_float r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other
    a - b
  end

  def *(other)
    Rubinius.asm do
      flt = new_label
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_1 r3
      n_ieq r3, r3, r2
      b_if r3, flt
      goto done

      flt.set!
      n_dmul r0, r0, r1
      r_store_float r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other
    a * b
  end

  def /(other)
    Rubinius.asm do
      flt = new_label
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_1 r3
      n_ieq r3, r3, r2
      b_if r3, flt
      goto done

      flt.set!
      n_ddiv r0, r0, r1
      r_store_float r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    redo_coerced :/, other
  end

  alias_method :divide, :/
  alias_method :quo, :/
  alias_method :fdiv, :/

  INFINITY = 1.0 / 0.0
  NAN = 0.0 / 0.0

  def divmod(other)
    Rubinius.primitive :float_divmod
    b, a = math_coerce other
    a.divmod b
  end

  def %(other)
    return 0 / 0.to_f if other == 0
    Rubinius.primitive :float_mod
    b, a = math_coerce other
    a % b
  end

  alias_method :modulo, :%

  def <(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_dlt r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other, :compare_error
    a < b
  end

  def <=(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_dle r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other, :compare_error
    a <= b
  end

  def >(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_dgt r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other, :compare_error
    a > b
  end

  def >=(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_dge r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce other, :compare_error
    a >= b
  end

  def <=>(other)
    Rubinius.primitive :float_compare
    b, a = math_coerce other, :compare_error
    a <=> b
  rescue ArgumentError
    nil
  end

  def !=(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_dne r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    b, a = math_coerce(other)
    a != b ? true : false
  end

  def ==(other)
    Rubinius.asm do
      done = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register
      r3 = new_register

      r_load_m_binops r0, r1

      n_promote r2, r0, r1

      r_load_0 r3
      n_ieq r3, r3, r2
      b_if r3, done

      n_deq r0, r0, r1
      r_load_bool r0, r0
      r_ret r0

      done.set!

      # TODO: teach the bytecode compiler better
      push_true
    end

    begin
      b, a = math_coerce(other)
      a == b ? true : false
    rescue TypeError
      return other == self
    end
  end

  def eql?(other)
    Rubinius.primitive :float_eql
    false
  end

  def nan?
    Rubinius.asm do
      r0 = new_register
      r1 = new_register

      r_load_self r0
      r_load_float r0, r0
      n_dnan r1, r0
      r_load_bool r0, r1
      r_ret r0

      # TODO: teach the bytecode compiler better
      push_true
    end
  end

  def infinite?
    Rubinius.asm do
      no = new_label

      r0 = new_register
      r1 = new_register
      r2 = new_register

      r_load_self r0
      r_load_float r0, r0
      n_dinf r1, r0

      r_load_0 r0
      n_ieq r2, r1, r0
      b_if r2, no

      r_store_int r0, r1
      r_ret r0

      no.set!
      r_load_nil r0, 0
      r_ret r0

      # TODO: teach the bytecode compiler better
      push_true
    end
  end

  def finite?
    not (nan? or infinite?)
  end

  def to_f
    self
  end

  def to_i
    Rubinius.primitive :float_to_i
    raise PrimitiveFailure, "Float#to_i primitive failed"
  end

  alias_method :to_int, :to_i
  alias_method :truncate, :to_i

  def to_s
    to_s_minimal
  end

  alias_method :inspect, :to_s

  def to_s_minimal
    Rubinius.primitive :float_to_s_minimal
    raise PrimitiveFailure, "Float#to_s_minimal primitive failed: output exceeds buffer size"
  end

  def to_s_formatted(fmt)
    Rubinius.primitive :float_to_s_formatted
    raise PrimitiveFailure, "Float#to_s_formatted primitive failed: output exceeds buffer size"
  end
  private :to_s_formatted

  def dtoa
    Rubinius.primitive :float_dtoa
    raise PrimitiveFailure, "Fload#dtoa primitive failed"
  end

  def to_packed(size)
    Rubinius.primitive :float_to_packed
    raise PrimitiveFailure, "Float#to_packed primitive failed"
  end

  def ceil
    int = to_i()

    return int if self == int or self < 0
    return int + 1
  end

  def floor
    int = to_i()

    return int if self > 0 or self == int
    return int - 1
  end

  def next_float
    return NAN if self.nan?
    return -MAX if self == -INFINITY
    return INFINITY if self == MAX
    return Math.ldexp(0.5, MIN_EXP - MANT_DIG + 1) if self.zero?

    frac, exp = Math.frexp self

    if frac == -0.5
      frac *= 2
      exp -= 1
    end

    smallest_frac = EPSILON / 2
    smallest_frac = Math.ldexp(smallest_frac, MIN_EXP - exp) if exp < MIN_EXP

    result_frac = frac + smallest_frac

    return -0.0 if result_frac.zero? && frac < 0
    return 0.0 if result_frac.zero? && frac > 0

    return Math.ldexp result_frac, exp
  end

  def prev_float
    return -(-self).next_float
  end
end