implement complex div/mul libcalls - #5319
folkertdev wants to merge 1 commit into
Conversation
| fn complex_binop<'tcx, S: Semantics>( | ||
| this: &mut MiriInterpCx<'tcx>, | ||
| op: ComplexFloatBinop<S>, | ||
| [a, b, c, d]: &[OpTy<'tcx>; 4], |
There was a problem hiding this comment.
These names are not very helpful... at the very least please document them. But I assume they correspond to the real and imaginary parts of the left and right input, respectively -- the names should reflect that to some extend, like [a_re, a_im, b_re, b_im] or so.
| let ac = (a * c).value; | ||
| let bd = (b * d).value; | ||
| let ad = (a * d).value; | ||
| let bc = (b * c).value; | ||
|
|
||
| let z = Complex::new((ac - bd).value, (ad + bc).value); |
There was a problem hiding this comment.
Isn't this duplicating naive_complex_mul?
There was a problem hiding this comment.
yes, but the ac etc. variables are used below.
| mag.copy_sign(x) | ||
| }; | ||
|
|
||
| let mut recalc = false; |
There was a problem hiding this comment.
Why is this so imperative? What's the point of this mutable variable?
There was a problem hiding this comment.
It is translated directly from compiler-rt, e.g.
| z.re = (z.re * IeeeFloat::<S>::INFINITY).value; | ||
| z.im = (z.im * IeeeFloat::<S>::INFINITY).value; | ||
|
|
||
| z |
There was a problem hiding this comment.
There's a lot going on in this function and none of it is explained. What exactly is the spec being implemented here? Why is "at least one non-NaN" sufficient to know that the "fast-path" is fine? What is the point of all the logic after that NaN check?
There was a problem hiding this comment.
This is specified in annex G of the C23 standard
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=556
I'll port over the comments, those are nice.
There was a problem hiding this comment.
That document also says
It is less than ideal in that undue overflow and underflow could occur.
What is that about?
There was a problem hiding this comment.
It is less than ideal in that undue overflow and underflow could occur.
I found https://arxiv.org/pdf/1210.4539 which presents some cases where the C specification algorithm still produces incorrect results. There is also https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1399.htm which presents more incorrect results. It proposes an overhaul to complex division that has clearly not been adopted.
There was a problem hiding this comment.
So far I don't even know what "correct" and "incorrect" mean. What is the specification for these operations? That's what the doc comment should explain. Without that, discussing correctness is pointless.
However it seems like correctness is not actually precisely defined and the functions are approximations. Doesn't that mean we should randomize them like we randomize sin, log, etc?
d48a831 to
2a59386
Compare
| /// | ||
| /// a+bi * c+di = ((ac + bd) + (bc - ad)i) / (c*c + d*d) | ||
| /// | ||
| /// But with recovery of infinities if the above expresion results in NaN + NaNi. |
There was a problem hiding this comment.
Please explain what "recovery of infinities" means, or reference a document that explains this.
There was a problem hiding this comment.
I've added some examples
| mut a: IeeeFloat<S>, | ||
| mut b: IeeeFloat<S>, | ||
| mut c: IeeeFloat<S>, | ||
| mut d: IeeeFloat<S>, | ||
| ) -> Complex<IeeeFloat<S>> { | ||
| let ac = (a * c).value; | ||
| let bd = (b * d).value; | ||
| let ad = (a * d).value; | ||
| let bc = (b * c).value; |
There was a problem hiding this comment.
These names are still completely meaningless.^^
There was a problem hiding this comment.
They represent a + bi and c + di, and ac is the product a * c? They are abstract but relate to the doc comment and this is, I think, the standard way to write this down?
There was a problem hiding this comment.
in other words: I don't think using lhs_re, lhs_re_mul_rhs_re etc is substantially clearer.
There was a problem hiding this comment.
Hm, I guess we're going to link to https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=556 which also uses a, b, c, d...
There was a problem hiding this comment.
Yeah, I've linked that now. Having done more reading, it's really not "the best" way of implementing these operations (especially division), but for these libcalls the C reference algorithm is what we should implement.
| let mut z = naive_complex_mul(a, b, c, d); | ||
| z.re = (z.re * IeeeFloat::<S>::INFINITY).value; | ||
| z.im = (z.im * IeeeFloat::<S>::INFINITY).value; |
There was a problem hiding this comment.
What is this doing?
| // Scale the inputs | ||
| let max = IeeeFloat::<S>::max(c.abs(), d.abs()); | ||
| let mut neg_ilogbw = 0; | ||
| if max.is_finite() && max != IeeeFloat::<S>::ZERO { | ||
| neg_ilogbw = max.ilogb().checked_neg().unwrap(); | ||
| c = c.scalbn(neg_ilogbw); | ||
| d = d.scalbn(neg_ilogbw); | ||
| } |
There was a problem hiding this comment.
What is happening here? Scale the inputs how and why?
There was a problem hiding this comment.
My theory is that it's about preventing overflow. I'll have to look into that more though.
There was a problem hiding this comment.
Hmm, the most I could find about it is from
In the ISO/IEC 9899:TC3 C99 standard [9], section G.5.1, “Multiplicative operators”, the authors present a _Cdivd function in the C language which implements complex division. According to Kahan [10] (in the Appendix, “Over/Underflow Undermines Complex Number Division in Java”), this code is due to Jim Thomas and Fred Tydeman.
The algorithm is based on a prescaling of c and d, such that their base-2 exponent is made zero. This scales the denominator c² + d² and avoids most overflows or underflows. The post-scaling produces then the correct result. This scaling is based on a power of 2, which avoids rounding.
Only in the case of an IEEE exception, the algorithm recomputes the division, taking into account for NaNs and Infinities. The code does not defend against overflow and underflow in the calculation of the numerator.
We will show later in this paper that the C99 algorithm performs nearly as well as Smith’s algorithm, but fails in many cases.
2a59386 to
779d463
Compare
| // Replace infinities with a signed unit value, and NaN with zero. | ||
| // This avoids invalid infinity arithmetic. |
There was a problem hiding this comment.
We're doing the inf replacement on a, b and the NaN replacement on c, d. Why that? The comment makes it sounds like we're doing both replacements on all values.
779d463 to
09b6791
Compare
09b6791 to
2dd9e42
Compare

This needs rust-lang/rust#162832 to be merged and synced for the
Div/Mulinstances.Maybe we can iterate on the implementation already, or if not this will just sit for a couple of days.