crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.32k stars 1.61k forks source link

[RFC] Complex-valued overloads of mathematical functions #10005

Open HertzDevil opened 3 years ago

HertzDevil commented 3 years ago

Complex overloads for these functions are missing compared to Ruby's CMath gem (used to be part of their standard library):

Additionally Complex#** is also missing. I propose adding these to the standard library, except for atan2.

It would be good if exponentiation is also defined with non-Complex types as the receiver (base) and the Complex value as the argument (exponent). I suggest the following overloads:

asterite commented 3 years ago

I actually think Complex should be moved to a shard. I know I'm biased, but I never used Complex in my life, in any language. So it seems Complex is mainly for very specific math domains. That means it's better if we move it to a shard.

HertzDevil commented 3 years ago

This might be why they decided to move these functions to a separate gem. The difference is that some of the functions are already in our Math. That means we now face another design choice: do we

stellarpower commented 1 year ago

On the shard argument, std::complex is in the standard library for C++, which has a pretty slim offering by default. Admittedly it's specialised for maths, signal processing, etc.; but I reckon it's quite important as things go in any case. I just encountered this trying to implement the FFT, and this is a core algorithm underpinning lots of things, so it ought to be accessible easily at least.

I definitely think it should more or less be all or nothing though. I was a bit thrown given that there's a complex overload for exp, but no ability to raise a complex number to a power itself. Which is kinda amusing when you consider the definition of i is that i²== -1. Be it in a shard, or in the standard library, I think a reasonably complete set of methods ought to be available.

stellarpower commented 1 year ago

Lazy and possibly mathematically imperfect workaround in case of any use:

struct Complex
    def **(other : Number) : Complex
        # Leverage the provision of `exp` overloaded for complex, whilst exponentiation of two numbers itself is missing.

        # If other is about 0, we risk taking the logarithm of zero and get numerical errors.
        return Complex.new(1, 0) if other.zero?

        exponent = other * Math.log(self)

        return exp(exponent)
    end
end
erdian718 commented 12 months ago

Here's another breaking change: The argument is a function of a real number, and the return value may also be a complex number. For example: sqrt(-1) = i, This changes the return value type of the original sqrt function.

So, I think it might be a bit problematic to add support for complex numbers directly under namespace Math.

It may be appropriate to refer to ruby's approach: implement basic operators (including **), but put mathematical functions in CMath shard.

erdian718 commented 12 months ago

One more question: the correct inheritance relationship regarding numbers should be:

Complex < Number
Real < Number
Float < Real
Int < Real

But in Crystal 1.x it's hard to change.

HertzDevil commented 12 months ago

Nunber is the type for real numbers. How much would the standard library benefit from making it also the base type of non-real numbers?

erdian718 commented 12 months ago

Nunber is the type for real numbers. How much would the standard library benefit from making it also the base type of non-real numbers?

It depends on the place of complex numbers in the programming language. Some languages put complex numbers on an equal footing with Int and Float, while others do not. There is no need to discuss it, I just want to point out that the place of Complex in Crystal is not the same as that of Int and Float, and we should not add support for complex numbers directly under Math namespace, this will be a bit problematic (the return type of some functions will change).

stellarpower commented 12 months ago

Here's another breaking change: The argument is a function of a real number, and the return value may also be a complex number. For example: sqrt(-1) = i, This changes the return value type of the original sqrt function.

I don't think it would be unreasonable to limit handling of complex numbers to overloads that have at least one complex parameter. In this case, sqrt(-1) would yield an error, whilst sqrt(-1 + 0i) would return 0 + i.

This enables a clear opt-in approach.

I haven't used ruby CMath, but given classes can be re-opened, if there's a way that involves requiring something that then modifies the standard library functions, this also would seem an elegant way to achieve it. From what I have heard though, this causes problems with third-party code.

erdian718 commented 11 months ago

In this case, sqrt(-1) would yield an error, whilst sqrt(-1 + 0i) would return 0 + i.

This is a good idea! @stellarpower