Skip to content

Conversation

@ish-kafel
Copy link

No description provided.

@ish-kafel
Copy link
Author

When the imaginary part of the result of this.acos() is greater than zero and the value is small, the conditional branch may fail. For example, the value of new Complex(0.451).acos().im is 1.1102230246251564e-16.

@infusion
Copy link
Collaborator

That's a nice finding! Thanks a lot! Could you please add test cases for this to be covered?

@ish-kafel
Copy link
Author

ish-kafel commented Oct 13, 2025

and does acot needs to replace atan2(1,a) with atan(1/a) when b==0 according to definition of Inverse Cotangent in Wolfram MathWorld?

@infusion
Copy link
Collaborator

atan2(1, a) is just another formulation of ´atan(1/a)` with the advantage of being more robust (no division by zero, keeping the orientation, and probably also numerically more stable). So no need to change that, they are mathematically identical.

@ish-kafel
Copy link
Author

but atan2(1, a) will return a positive number when a<0 which is different from atan(1/a)

@infusion
Copy link
Collaborator

infusion commented Oct 16, 2025

Thanks! I still believe atan2(1, a) is the correct choice for acot when b == 0.

For (real (a)): Let (\theta={atan2}(1,a)\in(0,\pi)). Then

$$a+i = r\,e^{i\theta},\quad a-i = r\,e^{-i\theta}\quad (r=\sqrt{a^2+1}),$$

so

$$\frac{a-i}{a+i}=e^{-2i\theta}\;\;\Rightarrow\;\; {acot}(a)=\frac{i}{2}\Log\!\left(\frac{a-i}{a+i}\right)=\theta ={atan2}(1,a).$$

This gives the expected range ({acot}(a)\in(0,\pi)).

Relation to atan(1/a):

$${atan2}(1,a)= \begin{cases} \arctan(1/a), & a>0,\\\ \pi/2, & a=0,\\\ \arctan(1/a)+\pi, & a<0. \end{cases}$$

For (a<0), atan(1/a) differs by (\pi) (it lands in ((-\pi/2,\pi/2))), while atan2 correctly places the angle in ((\pi/2,\pi)). At (a=0), atan(1/a) is undefined; atan2 is well-defined.

Bottom line: atan2(1, a) matches the definition-derived branch for acot, handles (a=0) safely, and selects the correct quadrant for (a<0).

@ish-kafel
Copy link
Author

but it will creates discontinuity near negative real number

new Complex(-1, 1e-15).acot() //-0.7853981633974483-5.551115123125786e-16i
new Complex(-1).acot()        //2.356194490192345
new Complex(-1, -1e-15).acot()//-0.7853981633974483+5.55111512312578e-16i

@infusion
Copy link
Collaborator

infusion commented Oct 16, 2025

Thanks for the example. The “discontinuity near the negative real number” you’re seeing is actually a consequence of the branch choice for acot, not a bug.

What this library does (by design):

  • On the real axis we use the conventional range (0, π) for acot(x):
    • acot(1) = π/4, acot(0) = π/2, acot(-1) = 3π/4, etc.
  • In the complex plane we take the principal branch (e.g. via [ acot z ;=; \tfrac{i}{2},\big(\log(z+i)-\log(z-i)\big) ]

With that choice, there is an unavoidable jump by π along the negative real axis. Your three examples are exactly what we expect:

new Complex(-1,  1e-15).acot() // ≈ -π/4  - i·0
new Complex(-1,       0).acot() // =  3π/4          (by the (0, π) real-range)
new Complex(-1, -1e-15).acot() // ≈ -π/4  + i·0

Approaching -1 from above/below the real axis lands you near −π/4 (principal complex limit), but the on-axis value - by our real-line convention - is 3π/4. That jump is the branch cut showing up; you can’t have both the (0, π) real-range and continuity across the negative real axis for a single-valued principal branch.

This is also why, in the real-only fallback, using atan2(1, a) is correct: it preserves the (0, π) convention and picks the right quadrant for negative a. Replacing it with atan(1/a) would silently switch to the other convention ((-π/2, π/2]) and give -π/4 at a = -1, contradicting the chosen real-axis behavior.

If you prefer continuity off the real axis around negatives, that’s a valid alternative, but it means adopting the other convention (((-π/2, π/2]) on ℝ) and updating expectations/tests accordingly. In other words, it’s a policy change, not a one-line fix.

I’m happy to add a note to the docs spelling this out, so this behavior is explicit.

@ish-kafel
Copy link
Author

Thank you so much for your patient explaination. When I first started using this library, I assumed it would handle complex numbers more continuously. I just searched for discussions about the principal branch of arccot(z) and found that it isn't strictly defined. Now, let's talk about this PR. I'm sorry I'm not familiar with test cases. Do you think I need to add any more test cases?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants