Open HertzDevil opened 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.
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
#**
in the standard library, and move the complex-valued functions in Math
(including existing ones e.g. #exp
) into a shard?Complex
and all complex-valued functions to a shard?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.
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
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.
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.
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?
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).
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 originalsqrt
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.
In this case, sqrt(-1) would yield an error, whilst sqrt(-1 + 0i) would return 0 + i.
This is a good idea! @stellarpower
Complex
overloads for these functions are missing compared to Ruby's CMath gem (used to be part of their standard library):Math.sin
,.cos
,.tan
, and their inverse and/or hyperbolic variantsMath.atan2
(it accepts two complex arguments?)Math.cbrt
Additionally
Complex#**
is also missing. I propose adding these to the standard library, except foratan2
.It would be good if exponentiation is also defined with non-
Complex
types as the receiver (base) and theComplex
value as the argument (exponent). I suggest the following overloads:Complex#**(Complex)
: The base overload.Complex#**(Float)
: Calls#to_f64
on the argument.Complex#**(Int)
: Fast exponentiation similar to the integer case, except negative exponents are allowed.Int#**(Complex)
: Casts the receiver into aComplex
.Float#**(Complex)
: Casts the receiver into aComplex
. This will be a breaking change becauseFloat32
andFloat64
already define#**
overloads with no type restrictions on the argument: