-
-
Notifications
You must be signed in to change notification settings - Fork 706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Issue 11652: support numerical ^^ complex operations in std.complex #1740
Conversation
|
Interesting, FreeBSD and Darwin seem to be failing for FP operations on the following unittests: auto rer = a ^^ complex(2.0, 0.0);
assert(rer.re == a ^^ 2.0);
assert(rer.im == 0.0);I know FP equality comparisons are risky, but I was rather hoping this one might be reliable ... oh well, back to approxEqual. :-P |
|
To understand what I'm getting at with respect to precision of calculation, consider the case of -1 raised to the power of 0.5 + 2i. This should result in a complex number with absolute value of exp(-2 * PI) and argument of PI/2. That is, theoretically, a number whose real part is 0 (argument PI/2) and whose imaginary part is exp(-2 * PI). Using the current opBinaryRight for numeric types to calculate while if one calculates So, the real part is further away from the "true" value of 0 by several orders of magnitude with the complex ^^ complex calculation. Further, if we look at absolute value and argument, we get: Calculating numeric ^^ complex: complex ^^ complex |
|
I've pushed an updated patch to avoid the failing unittest. I've preserved the |
| Complex!(CommonType!(T, R)) opBinaryRight(string op, R)(R lhs) const | ||
| if (op == "^^" && isNumeric!R) | ||
| { | ||
| FPTemporary!R r = std.math.abs(lhs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to get the abs of lhs, if the first thing we do is check it's sign? At the very least, I'd only calculate it in the second branch (if at all).
Unless my floating point-fu is too low, shouldn't this work just fine?
if (lhs >= 0)
{
return typeof(return)(lhs ^^ this.re, log(lhs) * this.im);
}
else
{
// theta = PI
FPTemporary!R r = -lhs;
FPTemporary!(CommonType!(T, R)) ab = r ^^ this.re * exp(-PI * this.im);
FPTemporary!(CommonType!(T, R)) ar = PI * this.re + log(r) * this.im;
return typeof(return)(ab * std.math.cos(ar), ab * std.math.sin(ar));
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I'll tweak.
|
This update should fix the errors identified for the internals of numeric ^^ complex and also address the absolute-value bits. I've reinstated some of the floating-point == floating-point unittests just to see how they roll now the internals are right; will force-push a corrected commit if they still fail. It's a bit irritating that the cos/sin functions are so slightly inexact that complex ^^ complex with imaginary part zero results in a complex number whose imaginary part is almost but not quite zero. Floating point, we love you. :-) |
|
Here's your extra unittest. I'll squash all of this when it's done, how is the rest of it looking? |
|
.... and the exact-equality unittest fails on 32-bit systems still. Oh well. Fixed, should pass everything now. |
|
Seems good to me overall. Give it a good squashing, it it'll be good to go. It's LGTM for me. But I wouldn't mind another reviewer giving it a thumbs up first before. |
|
Will do :-) It would be nice to get Lars or Don's input as the original module authors. |
Ping them: @kyllingstad , @donc . |
|
Well, the unittest which is currently failing (line 574) should pass in its current form, I think. That is, it should be safe to assume exact equality in that case. |
|
I agree, but it's not obvious why. It's failing on 32-bit systems (or at least, 32-bit Darwin and FreeBSD). What I could do, if you guys don't mind the extra testing weight, is to push a version that writefln's the values to screen so that we can check them. |
|
Let's see how these go. For the cases using |
|
Well, the results here look rather bizarre. The real parts really do look like they should be identical to the results of the Seeing this, I can't explain the equality failures that have been observed, so I'm going to reinstate equality comparisons where appropriate and see what happens. Meanwhile, does anyone else have any idea why I should see the results I have here but get equality failures? |
|
https://d.puremagic.com/test-results/pull.ghtml?projectid=1&runid=811862&logid=6 Are these not replayed on win 64?
Honestly, floating point equality in D is... strange. In particular, at the end of the day, if you are comparing something smaller than real, there is basically 0 guarantee that a comparison will succeed, even on equal objects, as one may be stored in a double, and the other, (during the comparison) temporarily in a more precise real. http://d.puremagic.com/issues/show_bug.cgi?id=8475 Relevant (I believe): double getFloat() pure
{
return sqrt(1.1);
}
void main()
{
writeln(isIdentical(getFloat(), getFloat())); //true
writeln(getFloat() is getFloat()); //true
writeln(getFloat() == getFloat()); //False, wtf?
}and The conclusion I came to is that unless a floating point represents an exact value, |
Well, they should be. If they're not, that probably means something about how Win64 handles output from the unittests.
So I see. Well, in that case I don't see what else I can do, except perhaps to switch to using |
|
As per Ali Çehreli's advice on D.learn, let's try outputting those results with |
|
OK, so we're definitely dealing with a bug with |
|
A note on why I just pushed another commit with assert(isIdentical(rer.re, a ^^ 2.0));which I guess could be put down to the fact that whereas So, I'm guessing temporary variables are essential anyway, so I might as well give |
|
Typical that the exact test run I most want to confirm the success or failure of, instead falls over on a thread priority error :-P |
|
Darwin32 seems to be OK with the unittests as they have become (if not with thread priority...), so squashing. Assuming the tests go all right, is this OK with everyone? :-) |
|
OK, I give up. Equality So, unless anyone can suggest an alternative, I think we have to concede the case to |
|
Last try before I revert to |
|
OK, looks like finally we have working unittests. Sorry for the noise and truckload of patches over what ought in principle to have been very simple. Anyway, @monarchdodra, @kyllingstad, how does this look? |
| if (!approxEqual(arg(rec1b), PI, EPS)) | ||
| { | ||
| assert(approxEqual(arg(rec1b), -PI, EPS)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: The if seems a little verbose, it could be reduced to simply one of:
//Result ≡ PI (mod 2PI)
assert(approxEqual(std.math.abs(arg(rec1b)), PI, EPS)));
assert(approxEqual(std.math.cos(arg(rec1b)), -1, EPS)));
assert(approxEqual(std.math.sin(arg(rec1b)), 0, EPS)));Furthermore, if you want to be very anal, I think the if could be wrong, in the sense that depending on where the rounding occurs in the call chain, the first result could be actually be -PI, whereas the second result could be PI
Doing this should be the simplest and most correct form, I think?
auto arg1b = arg(rec1b);
assert(approxEqual(arg1b, PI, EPS) || approxEqual(arg1b, -PI, EPS));There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Furthermore, if you want to be very anal, I think the
ifcould be wrong
Ahh, I see the concern. I don't mind trialling the abs of the argument; the reason I formulated it like this was so that code coverage analysis would let you see if there was any change in whether the imaginary part was turning out slightly negative or not.
Doesn't the simplest and most correct form you propose potentially fall victim to the same issue you identify with the use of if?
AFAIK, no. Since once stored in a variable, the rounding error will be bound to that variable's neighborhood, and in no way can it jump from I think. |
|
Ahh, sorry, I missed the separate variable. :-) Patch coming up! |
…operations.
The current design takes advantage of the fact that we know
we're dealing with a number whose argument is either 0 or PI.
An alternative design would be to just create a complex number
whose real part is equal to the input, and then raise it to
the power of this:
return typeof(return)(lhs, 0) ^^ this;
This would generate probably less precisely calculated values
but would be 100% consistent with complex ^^ complex calculations.
|
Here you go :-) Let's see whether the thread priority demon strikes this time ... |
|
Auto-merge toggled on |
|
We have auto-merge now? Superb! When did that happen? |
Roughly about a month ago? It's pretty nice yeah. Unfortunalty, darwin's ridiculously high failure rate does get into its way :/ |
Fix Issue 11652: support numerical ^^ complex operations in std.complex
This patch adds a Complex.opBinaryRight method for
op == "^^"and numeric LHS.The current design takes advantage of the fact that we know we're dealing with a number whose argument is either 0 or PI. An alternative design would be to just create a complex number whose real part is equal to the input, and then raise it to the power of this:
This would generate probably less precisely calculated values but would be 100% consistent with complex ^^ complex calculations. I leave the choice here up to review. :-)