Bug #5179
closedComplex#rationalize and to_r with approximate zeros
Description
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?
Updated by mrkn (Kenta Murata) over 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.
Updated by shyouhei (Shyouhei Urabe) almost 13 years ago
- Status changed from Open to Assigned
Updated by jeremyevans0 (Jeremy Evans) almost 5 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) almost 5 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) over 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) 11 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) 11 months ago
I'm working on to_r at https://github.com/ruby/ruby/pull/9581.
I will do for other methods later.