Fix NaN propagation in rc_sqrt and rc_log (mappers.cc) #46
Fix NaN propagation in rc_sqrt and rc_log (mappers.cc) #46AADARSHPUROHIT466 wants to merge 22 commits into
Conversation
Co-authored-by: Markus Mützel <markus.muetzel@gmx.de>
|
I haven't built with the patch yet. But would it fix the following? If it does, it would be nice to add a test for that in Also: Why does that only apply to Please, add self-tests for all four cases (single and double |
|
Thank you for the feedback. I've updated rc_sqrt and rc_log for both double and float types to handle NaN input consistently by returning Complex(NaN, NaN) or FloatComplex(NaN, NaN). Currently, Octave returns NaN + 0i for sqrt(NaN) and log(NaN), but per IEEE 754 and C++ standards (e.g., std::complex), the mathematically correct result is NaN + NaN*i, since operations on NaN should propagate NaN in both components unless specifically defined otherwise. I've added self-tests in liboctinterp/corefcn/mappers.cc to verify this behavior for both real and imaginary parts in single and double precision. While I was unable to build Octave locally on Windows due to setup constraints, I verified the expected logic using octave-online.net. The current unpatched version indeed returns NaN + 0i, which confirms that the existing behavior is inconsistent with standard expectations. Once the patch is applied, imag(sqrt(NaN)) and imag(log(NaN)) will correctly return NaN. I believe this change improves consistency with numerical standards and prevents silent loss of NaN in the imaginary part. Please let me know if any further changes or adjustments are needed. |
mmuetzel
left a comment
There was a problem hiding this comment.
Thank you for adding the tests.
I'm not quite sure why you are using vectors with four elements in the tests when you only check two or three of them. Either adapt the size of the vector to the number of elements that you like to check. Or add tests for all elements in the vector.
I haven't compiled with the changes yet either. I'll try to do that soon.
Co-authored-by: Markus Mützel <markus.muetzel@gmx.de>
Co-authored-by: Markus Mützel <markus.muetzel@gmx.de>
Co-authored-by: Markus Mützel <markus.muetzel@gmx.de>
41a3bba to
9f26282
Compare
|
Thank you for the feedback! I've updated the tests to: Verify all vector elements (as you suggested) The tests are now clearer, complete, and fully style-compliant. |
| %! assert (abs (imag (y(1)) - single (pi)) < 1e-5); # log(-1) ≈ πi | ||
| %! assert (isinf (real (y(2))) && real (y(2)) < 0); # log(0) = -Inf |
There was a problem hiding this comment.
See the comments to the tests for double precision.
| %! assert (isnan (real (y(4))), true); # real(NaN) is NaN | ||
| %! assert (isnan (imag (y(4))), true); # imag(NaN) is NaN |
There was a problem hiding this comment.
See the comment on the check on y(4) in the other test.
| %! assert (isnan (real (y(4))), true); # NaN → NaN | ||
| %! assert (isnan (imag (y(4))), true); |
There was a problem hiding this comment.
See the comment on the check on y(4) in the other test.
| %! assert (isnan (real (y(4))), true); # NaN → NaN | ||
| %! assert (isnan (imag (y(4))), true); |
There was a problem hiding this comment.
See the comment on the check on y(4) in the other test.
| %!test | ||
| %! x = single ([-1, 0, 1, NaN]); | ||
| %! y = log (x); | ||
| %! assert (abs (imag (y(1)) - single (pi)) < 1e-5); # log(-1) ≈ πi |
There was a problem hiding this comment.
Like for the test with double precision: Use three-argument assert for the test with tolerance.
Also use sqrt (eps ('single')) as the tolerance.
| %! assert (abs (imag (y(1)) - single (pi)) < 1e-5); # log(-1) ≈ πi | |
| assert (imag (y(1), -single (pi), sqrt (eps ('single'))); # log(-1) ≈ πi |
| %!test | ||
| %! x = single ([-1, 0, 1, NaN]); | ||
| %! y = sqrt (x); | ||
| %! assert (y(1), i, 1e-6f); # sqrt(-1) ≈ i |
There was a problem hiding this comment.
Is this a typo? (What does "f" mean here?)
| %! assert (y(1), i, 1e-6f); # sqrt(-1) ≈ i | |
| %! assert (y(1), i, 1e-6); # sqrt (-1) ≈ i |
There was a problem hiding this comment.
Ah. I think I understand now what you meant to do:
| %! assert (y(1), i, 1e-6f); # sqrt(-1) ≈ i | |
| %! assert (y(1), single (1i)); # sqrt (-1) = i |
You shouldn't need a tolerance if you write it that way.
|
Looking at the surrounding functions in |
|
I finally came around to build with the changes from this PR (as of 9f26282). |
|
Hmm. Unfortunately, this causes a regression: This shouldn't expand to a complex number. |
b36187e to
580f00f
Compare
|
Thank you for testing and catching the regression! You're absolutely right. sqrt(NaN) and log(NaN) should not promote to complex numbers — they should maintain scalar behavior (NaN + 0i). I've fixed the functions to return Complex(x, 0.0) or FloatComplex(x, 0.0f) for NaN inputs instead of Complex(x, x). This preserves the current scalar behavior while ensuring consistent NaN propagation in the real part. I've also updated the self-tests to expect imag(NaN) == 0 (matching existing behavior) while verifying isnan(real(NaN)). Now: text sqrt(NaN) → NaN + 0i (no regression) |
|
|
Thanks for the review. The PR has 38 conversations and 22 commits — could you please take another look and share the changes from one commit so I can address them more clearly? @mmuetzel |
|
In its current form, this PR doesn't change any behavior. It just complicates the implementation (and probably slows down the execution). What is the actual issue that you would like to address with this PR? Also, the new adapted tests don't look like they check for the expected behavior to me. Overall, this PR doesn't add any value and adds incorrect tests in its current state. |



These functions previously returned partial NaN values (
NaN + 0i) when input was NaN.This patch explicitly checks
std::isnan(x)and returnsComplex(x, x), ensuring bothreal and imaginary parts propagate NaN consistently. Verified with test cases in Octave REPL:
rc_sqrt(NaN) -> NaN + 0i
rc_log(NaN) -> NaN + 0i