Bug #5179


Complex#rationalize and to_r with approximate zeros

Added by marcandre (Marc-Andre Lafortune) almost 13 years ago. Updated 5 months ago.

Target version:
ruby -v:


Currently, Complex#rationalize and Complex#to_r raise a RangeError if the imaginary part is nonzero or is a Float. Note that a BigDecimal(0) is accepted, though:

Complex(1, 0).to_r                 # => Rational(1,1)
Complex(1, BigDecimal("0.0")).to_r # => Rational(1,1)
Complex(1, 0.0).to_r               # => RangeError

This is inconsistent. I recommend not raising an error for 0.0 (Float or BigDecimal). Any objection?

Related issues 1 (0 open1 closed)

Blocked by Ruby master - Feature #5321: Introducing Numeric#exact? and Numeric#inexact?Rejectedmatz (Yukihiro Matsumoto)Actions

Updated by mrkn (Kenta Murata) almost 13 years ago

  • Assignee set to mrkn (Kenta Murata)

0.0 doesn't exactly represent zero. It may be 0.0+-10.0**(Float::MIN_10_EXP-17).
BigDecimal(0) doesn't exactly represent zero, too.

I believe this issue should be resolved by introducing Numeric#exact? and/or Numeric#inexact? methods.

Actions #2

Updated by shyouhei (Shyouhei Urabe) over 12 years ago

  • Status changed from Open to Assigned

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago

As Numeric#exact? has been rejected, and BigDecimal is not a core class, I'm not sure what to do about this issue. @mrkn (Kenta Murata) seems to recommend RangeError for Complex(1, BigDecimal("0.0")).to_r (the same as with Complex(1, 0.0).to_r ). I think the bigdecimal library would have to override Kernel#Complex for that behavior. Would that be considered acceptable?

Updated by mame (Yusuke Endoh) over 4 years ago

We discussed #5321 at the dev-meeting. Whether a value is exact or inexact, is not decidable based on a class. For example, a literal 0.0 may be considered as precise and exact. But, if the same value is gained from an inaccurate calculation, it may be considered as inexact.

In regard to this paricular issue, Naruse pointed out that we already have Float#to_r. So I think that it is reasonable that Complex(1, 0.0).to_r returns Rational(1, 1) instead of RangeError.

Updated by kjtsanaktsidis (KJ Tsanaktsidis) about 1 year ago

I've explored the behaviour here a bit, and I think I do believe that 0.0 really does represent the concept of "zero". Specifically, it's the number that satisfies the following properties, for all possible non-NaN floats N:

  • abs(0.0 * N) == 0.0
  • 0.0 + N == N

You might obtain a bit pattern for 0.0 from the result of a calculation which might have otherwise produced a non-zero result if there was more precision; however that doesn't change the fact that the bit pattern you have really does satisfy the mathematical properties of zero.

Additionally, I think the distinction that complex.c currently draws between floating-point and non-floating-point zero also results in some other surprising results:

irb(main):030:0> Complex(3, 7) ** 0.0
=> (1.0+0.0i)
irb(main):031:0> Complex(3, 7) ** 0
=> (1+0i)
irb(main):032:0> Complex(3.2, 7) ** 0
=> (1+0i)
irb(main):033:0> Complex(3.2, 7) ** 0.0
=> (1.0+0.0i)

Why does the type of A ** B depend on whether B is a float or not, but not A?

irb(main):034:0> Complex(3.2, 7) ** Complex(1, 0.0)
=> (3.200000000000001+6.999999999999999i)
irb(main):035:0> Complex(3.2, 7) ** Complex(1, 0)
=> (3.2+7i)

This doesn't need to accumulate floating point error - the code in rb_complex_pow special-cases (a + bi) ** (c + 0i) -->(a + bi) ** c, but the special-casing is skipped if 0i is 0.0i instead.

Also, #to_i and #to_f have the same issue as #to_r:

irb(main):037:0> Complex(3, 0.0).to_i
(irb):37:in `to_i': can't convert 3+0.0i into Integer (RangeError)
irb(main):038:0> Complex(3, 0.0).to_f
(irb):38:in `to_f': can't convert 3+0.0i into Float (RangeError)

This is especially odd for #to_i because it of course has no problems truncating away the real part:

irb(main):040:0> Complex(3.1, 0).to_i
=> 3

I think we should change all the checks for k_exact_zero_p in complex.c to f_zero_p; i.e. in all the places where complex.c special-cases integer zero, also make it special-case floating-point zero. If people agree with this (especially people who actually use Complex in their code - I have pretty much never used it!) I can send a pretty simple patch to cloe this out.

Updated by mrkn (Kenta Murata) 6 months ago

Complex(re, im.to_r).to_r looks good to me if im is not 0 or 0r. Complex(re, im.to_i).to_i is also OK for me in the same manner.

Complex(re, im.to_f).to_f cannot resolve the problem the current to_f holds. I don't have other ideas to resolve this right now.

I don't think we can discuss ** along with to_r because ** is not type conversion.

Updated by mrkn (Kenta Murata) 6 months ago

I'm working on to_r at
I will do for other methods later.

Actions #8

Updated by Anonymous 5 months ago

  • Status changed from Assigned to Closed

Applied in changeset git|54a5b829442eb7ed1f4a2794ebb26696cf784e64.

Handle zero-like imaginary part as zero in to_r (#9581)

Fixes [Bug #5179]


Also available in: Atom PDF