mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

power returns invalid complex number for negative base and Infinity as exponent

Open balagge opened this issue 8 years ago • 25 comments

Following inputs return NaN - aNi in Notepad. Seems like all negative bases yield the same:

(-0.5)^Infinity (-1)^Infinity (-2)^Infinty

probably the underlying function pow() returns a Complex with both Real and Imaginary part set to NaN. (haven't checked).

Definitely, NaN + aNi is wrong. But whether this is simply an issue with the rendering, or the underlying logic, is to be analyzed. Some thoughts:

  • Whether the result should be a Complex with both Real and Imaginary parts set to NaN, is questionable (after all there is a NaN already, no need to introduce a "second, complex NaN" value maybe).

  • for -1 < a < 0, the result should probably be zero, as in javascript, this one converges: Math.pow(-0.5,Infinity) // 0

  • for a < -1 the result should not be Infinity (so, unlike javascript), because this is clearly divergent. (I don't understand javascript behavior: Math.pow(-2,Infinity) // Infinity

  • -Infinity as exponent should also be considered (currently these cases also return incorrect NaN + aNi, but convergence / divergence should also be considered separately for -Infinity)

balagge avatar Feb 28 '17 11:02 balagge

Thanks for reporting Paal. That NaN - aNi looks like a formatting issue. Besides that we can definitely give these edge cases with NaN and infinity some more thought. We should keep in mind that we're working with a numerical system (with it's limitations).

josdejong avatar Feb 28 '17 19:02 josdejong

I had a look into this: the pow function checks if the base is positive or the exponent is an integer or config.predictable = true and if so returns the result of Math.pow. Otherwise the pow function of Complex.js is used which returns Nan + NaNi for infinite exponents.

Therefore with config.predictable = true negative bases raised to the power of infinity return either Infinity or zero.

I feel that the behaviour should not change in this way regarding config.predictable so it would be worth adding a check for infinite exponents to the pow function.

harrysarson avatar Apr 20 '17 11:04 harrysarson

@HarrySarson would you like to pick this issue up?

josdejong avatar Apr 20 '17 18:04 josdejong

Sure I can have a go :)

harrysarson avatar Apr 20 '17 18:04 harrysarson

:+1: thanks

josdejong avatar Apr 20 '17 18:04 josdejong

What are your views on the best result for (-2)^Infinity?

  • a) Infinity as javascript, matlab and c++ all go for.
  • b) Not infinity as @balagge says - possibly NaN?.
  • c) Some sort of ComplexInfinity which how it is done in wolfram alpha.

I feel like (c) is the most elegant solution but would require either a new type or and extension of complex.js. (a) and (b) are much easier to do.

Additionally the behaviour of complex.js is currently to return (NaN + NaN * i) for any infinite powers - it would be nice if math.pow(2, Infinity).toString() === math.pow(math.complex(2), Infinity)).toString() so I might raise an issue with complex.js for better support for infinities.

harrysarson avatar Apr 20 '17 23:04 harrysarson

I think returning complex infinity for (-2)^Infinity would be most neat, or else complex NaN like it does now.

josdejong avatar Apr 22 '17 11:04 josdejong

For complex numbers NaN can make sense, since the the power function is defined via sin/cos and sin(inf)=nan. However, if we think about it, pow(c∈C, inf) is an infinite rotation of an arrow. With this interpretation, Infinity would make sense too. However, I would go with a new "symbol" - "complex infinity" as well.

I made some minor changes on complex.js regarding the infinity behavior. I'd be glad if you guys could have a look: https://github.com/infusion/Complex.js/issues/5

infusion avatar Apr 22 '17 22:04 infusion

Ah, and about the printing error, I catch NaN's in complex.js's toString(). I suspect, that we added an own stringifier for math.js.

infusion avatar Apr 22 '17 22:04 infusion

I've fixed the wrong formatting of NaN + aNi which should be NaN + NaNi. math.js indeed still overwrites the .toString of Complex.js. Maybe that's not needed at all. Let's check that out.

Sounds good @infusion . It may make sense to create a special constant complex infinity rather than using Infinity. It may make sense to create this as a constant in math.js: math.ComplexInfinity, which would simply be the new Complex.Infinity constant of Complex.js. Probably we can do the same for complex NaN: ComplexNaN?

josdejong avatar Apr 24 '17 06:04 josdejong

@josdejong for "NaN + NaNi", I decided for complex.js to make the whole complex-number NaN as soon as one component becomes NaN. So, the output is "NaN". I think this lacks some information in the output, but at the end it's just a feedback, that an operation failed, other than for infinity, where the individual dimensions can become infinity with a certain meaning.

Mapping Complex.Infinity to math.ComplexInfinity sounds good to me :)

ComplexNaN is still a big question. We actually use complex numbers to make Math.sqrt(-1)!=NaN work. There're only rare discontinuities where NaN can occur. That are 0/0 and the strange case when 0*Infinity is NaN in Javascript, which we can fix. For my last push to the "infinity" branch of complex.js, I decided to make 0/0 complex infinity as well, since I don't like to introduce a new symbol for that.

What do you guys think?

infusion avatar Apr 24 '17 10:04 infusion

I think complex infinity is a great idea. Complex infinity is different from "real" infinities Infinity and -Infinity, because those have directions on the complex plane, whereas complex infinity is directionless.

Complex infinity (denoted by I below) should satisfy:

  • I + z = I, for any z ∈ ℂ;
  • -I = I (UNLIKE real Infinities!!);
  • I * I = I;
  • I * z = I for any z ∈ ℂ, z ≠ 0;
  • z / I = 0 for any z ∈ ℂ, z ≠ 0;

Edge cases:

  • I + I is not defined (NaN), UNLIKE real Infinities!;
  • 0 * I is not defined (NaN?)
  • I / I is not defined (NaN?)
  • I - I is not defined (NaN?)

balagge avatar Apr 24 '17 12:04 balagge

ComplexNaN, on the other hand, maybe should be avoided. I do not see justification for a "separate" NaN value. (for the record: I do not see justification for NaN in the first place). But accepting the fact that we do have NaN, for whatever reason, then creating another one seems awkward to me.

So if any operation could/should return "complex NaN", I would vote for returning simply 'NaN' instead.

balagge avatar Apr 24 '17 12:04 balagge

http://reference.wolfram.com/language/ref/ComplexInfinity.html

This page is also useful, it has some return values in Wolfram of standard functions on ComplexInfinity. (under "Neat Examples")

balagge avatar Apr 24 '17 12:04 balagge

If the complex infinity is in the exponent, I would say:

  • z ^ I = I, for any z ∈ ℂ, abs(z) >1 (this includes I^I = I as well);
  • z ^ I = 1, if z = 1;
  • z ^ I is undefined for any z ∈ ℂ, abs(z) = 1 and z ≠ 1;
  • z ^ I = 0 for any z ∈ ℂ, abs(z) <1

update: these may be (are probably) wrong, someone should check it who has better knowledge of complex analysis than me... I have studied this type of stuff more than 2 decades ago.

balagge avatar Apr 24 '17 12:04 balagge

The discussion about ComplexInfinity may be better placed at infusion/Complex.js#5 as the implementation of complex pow is handed there.

complex(0).mul(Infinity) shouldn't simply return NaN as complex(0).mul(Infinity).add(4) should propagate the NaN value (in a similar way to 0*Infinity+5) where as if "normal" NaN was returned the user would have to check the result of every function call which would be very annoying for them.

Apart from that I agree :)

harrysarson avatar Apr 24 '17 19:04 harrysarson

Makes sense. So to summarize: we want Infinity, ComplexInfinity, and NaN but no "complex NaN".

The latter may be tricky since that means that after every operations with complex numbers we have to check whether the resulting complex number contains NaN for one or both re and im. What we could do though is adjust the presentation of a complex number containing NaN (like Complex.js already does), and adjust equality checks such that math.equal(math.complex(NaN, NaN), NaN) === true and function isNaN handles complex NaN instances too.

josdejong avatar Apr 24 '17 19:04 josdejong

Complex.js now has infinity support in master branch. When the next release is published to npm we should be able to fix math.pow(complex, Infinity).

I think it should "just work" but some tests might fail and even if they don't we should add some new tests. I will make a PR when the new version is published. :)

harrysarson avatar Feb 01 '18 16:02 harrysarson

I thik the concept of complex infinity must be introduced for math.js as well. (with a special symbol or sth)

infusion avatar Feb 01 '18 19:02 infusion

Great if you can pick this up coming time Harry !

josdejong avatar Feb 03 '18 14:02 josdejong

Hi Team - just picking up this issue to see if I can help.

First, I've confirmed via the Math notepad that the following two cases work as expected. Please see testing results: #804 Testing

  • lim n-> inf (-0.5)^{n} = 0
  • lim n-> -inf (-3)^{n} = 0

Just to clarify from reading previous comments - I need to use complex.js (https://github.com/infusion/Complex.js/#complexinfinity) for cases that don't converge to a limit, e.g. (-1)^{+-inf}, so instead of returning NaN + NaNi I simply return complex.INFINITY and update the unit tests accordingly?

Let me know what you think.

Best regards George

georgemarklow avatar Jan 03 '21 20:01 georgemarklow

Thanks for picking this up @georgemarklow !

Math Notepad is a little bit behind (still at mathjs v7.5.1). To know for sure what the output is it may be best to try out directly in the developer console of https://mathjs.org or checkout the project and use the repl locally. Also, be careful not to look at the output of format but really at the numeric output of math.pow.

It would be nice if you can get a clear list with the edge cases we're talking about, the desired outcome, and the current outcome. (At least I myself do not have the edge cases and desired outcome clear at this moment, and we may want to discuss some of them).

Indeed the calculations are done by Complex.js, in mathjs we want to do as little as possible, though when the output of Complex.js for those edgecases differs from what we want/expect, we have to write code for it in mathjs to cover the edge case.

josdejong avatar Jan 06 '21 09:01 josdejong

There is a stalled PR https://github.com/infusion/Complex.js/pull/25. @georgemarklow having a look at this might be a good place to start. It might be that we can patch the Complex infinity handling from within mathjs somehow although I suspect better infinity support from Complex.js will really be needed to make progress.

harrysarson avatar Jan 06 '21 16:01 harrysarson

Hi guys, following infusion/Complex.js#25, I created a branch off develop, wrote unit tests to handle the cases, and refactored the code to understand the problem and get feedback.

Could I have permission to create a new remote branch (see below)?

Thanks

Screenshot 2021-01-10 at 3 47 28 pm

georgemarklow avatar Jan 10 '21 15:01 georgemarklow

@georgemarklow what we normally do is start with forking the mathjs project to your own github account, create a feature branch there to work on something new, and then create a PR to josdejong/mathjs. When you're involved in the project for a longer time we can discuss becoming a collaborator on the project with access to the project itself, that would be great :).

josdejong avatar Jan 15 '21 19:01 josdejong