Bug #1708
closedrequire 'complex' Causes Unexpected Behaviour
Description
=begin
1.9 has Complex in core, yet on 1.8 you had to explicitly require it with "require 'complex'". However, 1.9 also has a 'complex.rb' in its lib directory which in turn requires 'cmath.rb'. 'cmath.rb' redefines quite a few Math methods. This is particularly confusing because you would expect "require 'complex'" to be a no-op on 1.9, whereas it actually causes Math methods to fail in mysterious ways.
For example, where Math used to raise TypeErrors it now raises NoMethodErrors. This happens for all of the overridden methods.
>> Math.atan(nil)
TypeError: can't convert nil into Float
from (irb):1:in `atan'
from (irb):1
from /usr/local/bin/irb:12:in `<main>'
>> require 'complex'
=> true
>> Math.atan(nil)
NoMethodError: undefined method `real?' for nil:NilClass
from /usr/local/lib/ruby/1.9.1/cmath.rb:145:in `atan'
from (irb):3
from /usr/local/bin/irb:12:in `<main>'
As Complex is in core Math methods should work with complex numbers by default. This half-in-, half-out-, approach is confusing. If the current behavior is intentional, it needs to be clarified in the Math and Complex documentation.
=end
Updated by matz (Yukihiro Matsumoto) over 15 years ago
=begin
Hi,
In message "Re: [ruby-core:24126] [Bug #1708] require 'complex' Causes Unexpected Behaviour"
on Fri, 3 Jul 2009 20:01:54 +0900, tadayoshi funaba redmine@ruby-lang.org writes:
|not a bug
Let me clarify. As Complex numbers made built-in in 1.9, Tadayoshi
asked me to make Math functions work well with complex numbers, but I
did want to keep semantics taken libm functions. So he separated
those functions into cmath.rb. And required it from complex.rb to
make 1.8 compatibility.
So options are:
- make those functions complex numbers aware from the beginning.
lose libm semantics and compatibility a bit. - keep them as they are. they are at least compatible with 1.8.
- stop requiring cmath.rb from complex.rb. lose compatibility.
keep libm semantics. - something else.
Opinion?
matz.
=end
Updated by runpaint (Run Paint Run Run) over 15 years ago
=begin
Thank you for the explanation, matz. :-)
When I heard that Complex had been moved to core I assumed that all Complex functionality had been translated, include the Math extensions. I imagine that this was a common reaction.
It would surely be optimal for the Math methods to seamlessly support Complex numbers. Let's take Math.cos for example. Passing it a Complex number without 'cmath':
>> Math.cos(Complex(1,2))
RangeError: can't convert 1+2i into Float
from (irb):79:in `to_f'
from (irb):79:in `cos'
from (irb):79
from /usr/local/bin/irb:12:in `<main>'
Passing it a Complex number with 'cmath':
>> Math.cos(Complex(1,2)).inspect
=> "(2.0327230070196656-3.0518977991517997i)"
In this case, what would be the harm of including 'cmath' by default? Performance isn't affected for the common cases (non-Complex numbers), yet Complex arguments "do the right thing". Indeed, the majority of the methods in 'cmath' appear to be only act differently from the default if their argument is Complex (and, in some cases, positive, too). What is the benefit to the user of having a method raise an exception when it could produce the right answer? Or, what do we lose by integrating 'cmath'?
Unless there are serious incompatibilities or performance concerns, it seems more robust and well-rounded if Complex numbers work as one would expect.
=end
Updated by matz (Yukihiro Matsumoto) over 15 years ago
=begin
Hi,
In message "Re: [ruby-core:24129] [Bug #1708] require 'complex' Causes Unexpected Behaviour"
on Sat, 4 Jul 2009 02:10:15 +0900, Run Paint Run Run redmine@ruby-lang.org writes:
|When I heard that Complex had been moved to core I assumed that all Complex functionality had been translated, include the Math extensions. I imagine that this was a common reaction.
According to the fail-early principle, unexpected situation should be
told (by raising an error) as early as possible. When the program do
not expect complex number calculation (or negative argument to sqrt),
it should cause an error, rather than continuing calculation with
complex numbers. Requiring 'complex' (or 'cmath') gives a chance to a
program to declare it is expecting complex number calculation.
Probably I think this way mostly because I have never had a chance to
use complex numbers excepct for testing the implementation. Those who
use complex numbers often might feel differently.
Of course I understand what you claim. It's a matter of trade-offs.
Tadayoshi would feel happy, I think, if you can persuade me.
matz.
=end
Updated by runpaint (Run Paint Run Run) over 15 years ago
=begin
According to the fail-early principle, unexpected situation should be
told (by raising an error) as early as possible.
I'd always considered that to apply to calculations that would ultimately fail, the logic being that you should complain about arguments that would cause failure up front, rather than attempting to perform an expensive calculation with them and then failing. In this case, we don't need to fail. We can give the right answer.
Let's use Math.sqrt(-2) for an example. Without 'cmath' the programmer's only option would be to rescue Errno::EDOM. With 'cmath' they could achieve the same effect by inspecting the result's #real? method, or they could perform mathematical operations on the result. With Complex implementing so many Numeric methods, it's perfectly possible for their code to continue to work with complex results. The difference being that now exceptions are much rarer. Integration of 'cmath' gives the programmer more control. Put another way, one can perform many more operations with a Complex object than he can with an Exception object.
Further, Math.sqrt(-2) raising an exception seems dishonest. The answer is perfectly knowable and calculable; we'd raise an exception not because the input was exceptional but because, presumably, we wanted to protect the programmer from calling a method on the result that was allowed for reals, but disallowed for non-reals. That is, we'd want to protect the programmer from saying:
Math.sqrt(n) > LIMIT # Raises a NoMethodError for :> if .sqrt returns a Complex
Or:
side = Math.sqrt(area_of_square)
# ....
cost = side * cost_per_side # cost may be a complex number...
Note that a similar argument could be levied against methods that occasionally returned Floats, too. A programmer who blindly calls #odd? on the result will have the same shock as if he blindly called a Fixnum method on a result which was occasionally complex.
Lastly, if we're considering programmers who may not expect to receive complex results from Math methods, we must also consider those who, knowing that Complex is in core, have the contrary expectation... ;-)
And now I'll be quiet to give other parties a chance to participate. ;-)
=end
Updated by tadf (tadayoshi funaba) over 15 years ago
=begin
ruby is a great believer in unix/c.
Math.acosh(-1) # EDOM
(-8) ** 0.5 #=> NaN
1 / 2 #=> 0
and
require 'cmath'
CMath.acosh(-1) #=> (0.0+3.141592653589793i)
Complex(-8) ** 0.5 #=> (1.7318549141438708e-16+2.82842712474619i)
Float(1) / 2 #=> 0.5
Rational(1) / 2 #=> (1/2)
it's too hard.
in scheme:
(acosh -1) ;=> 0.0+3.141592653589793i
(expt -8 0.5) ;=> 1.7318549141438708e-16+2.82842712474619i
(/ 1 2) ;=> 1/2
it's so simple.
i think that introducing complex number means extending the domain of ruby's numeric system.
we should provide proper results in the domain.
we should not restrict any general operations.
we can always use "div" instead of "/" for integer division.
and we can also provide FMath.sqrt as a restricted version, if you want.
of course, this is an option.
i'm looking forward to 2.0.
=end
Updated by yugui (Yuki Sonoda) over 15 years ago
- Status changed from Open to Rejected
- Assignee set to tadf (tadayoshi funaba)
=begin
Was the issue rejected? right?
=end