diff --git a/library/core/src/num/complex.rs b/library/core/src/num/complex.rs index 66126c52fadad..c6a56285cdfb1 100644 --- a/library/core/src/num/complex.rs +++ b/library/core/src/num/complex.rs @@ -1,4 +1,5 @@ -use crate::ops::{Add, Neg, Sub}; +use crate::num::imp::libm::complex::*; +use crate::ops::{Add, Div, Mul, Neg, Sub}; /// A complex number. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -91,3 +92,54 @@ impl> Sub for Complex { Complex::new(self.re - rhs, self.im) } } + +macro_rules! impl_complex_mul_div { + ($ty:ty, $mul:ident, $div:ident) => { + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Mul for Complex<$ty> { + type Output = Self; + + #[inline] + fn mul(self, rhs: Self) -> Self::Output { + let Complex { re: a, im: b } = self; + let Complex { re: c, im: d } = rhs; + + let ac = a * c; + let bd = b * d; + let ad = a * d; + let bc = b * c; + + let z = Complex::new(ac - bd, ad + bc); + + // Only call the libcall when both components are NaN. + // + // The naive algorithm would return NaN + NaNi for an input like + // (1 + 0i) * (inf + infi). The libcall instead returns inf + infi. + // + // We duplicate the fast path here so that it can be inlined. We use a libcall + // for the NaN correction to reduce the size of `core`. + if z.re.is_nan() && z.im.is_nan() { + crate::hint::cold_path(); + $mul(a, b, c, d) + } else { + z + } + } + } + + #[unstable(feature = "complex_numbers", issue = "154023")] + impl Div for Complex<$ty> { + type Output = Self; + + #[inline] + fn div(self, rhs: Self) -> Self::Output { + $div(self.re, self.im, rhs.re, rhs.im) + } + } + }; +} + +impl_complex_mul_div!(f16, __rust_mulhc3, __rust_divhc3); +impl_complex_mul_div!(f32, __mulsc3, __divsc3); +impl_complex_mul_div!(f64, __muldc3, __divdc3); +impl_complex_mul_div!(f128, __rust_multc3, __rust_divtc3); diff --git a/library/core/src/num/imp/libm.rs b/library/core/src/num/imp/libm.rs index 388f1479b2461..8e035dc516260 100644 --- a/library/core/src/num/imp/libm.rs +++ b/library/core/src/num/imp/libm.rs @@ -75,6 +75,27 @@ unsafe extern "C" { pub(crate) safe fn truncf16(x: f16) -> f16; } +/// These symbols are always provided by compiler-builtins. +pub(crate) mod complex { + use crate::num::Complex; + + unsafe extern "C" { + pub(crate) safe fn __mulsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __muldc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + + pub(crate) safe fn __divsc3(a: f32, b: f32, c: f32, d: f32) -> Complex; + pub(crate) safe fn __divdc3(a: f64, b: f64, c: f64, d: f64) -> Complex; + } + + unsafe extern "Rust" { + pub(crate) safe fn __rust_mulhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_multc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + + pub(crate) safe fn __rust_divhc3(a: f16, b: f16, c: f16, d: f16) -> Complex; + pub(crate) safe fn __rust_divtc3(a: f128, b: f128, c: f128, d: f128) -> Complex; + } +} + /// These symbols will be available when `std` is available, and on many no-std platforms. However, /// since this isn't a guarantee, we cannot rely on them for stable implementations. pub(crate) mod likely_available { diff --git a/library/coretests/tests/num/complex.rs b/library/coretests/tests/num/complex.rs index c22c5b9575b3d..ea7bff0acc7de 100644 --- a/library/coretests/tests/num/complex.rs +++ b/library/coretests/tests/num/complex.rs @@ -73,3 +73,63 @@ fn complex_negation() { assert_eq!(-Complex::new(1.0, -2.0), Complex::new(-1.0, 2.0)); assert_eq!(-Complex::new(1.0, f32::INFINITY), Complex::new(-1.0, f32::NEG_INFINITY),); } + +#[test] +fn complex_multiplication() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f32, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(Complex::new(1.0f64, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(1.0f128, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f16::INFINITY, f16::INFINITY), + Complex::new(f16::INFINITY, f16::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f32::INFINITY, f32::INFINITY), + Complex::new(f32::INFINITY, f32::INFINITY) + ); + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f64::INFINITY, f64::INFINITY), + Complex::new(f64::INFINITY, f64::INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(1.0, 0.0) * Complex::new(f128::INFINITY, f128::INFINITY), + Complex::new(f128::INFINITY, f128::INFINITY) + ); +} + +#[test] +fn div() { + #[cfg(target_has_reliable_f16)] + assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f32, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(Complex::new(2.0f64, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + #[cfg(target_has_reliable_f128)] + assert_eq!(Complex::new(2.0f128, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0)); + + // The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it. + #[cfg(target_has_reliable_f16)] + assert_eq!( + Complex::new(f16::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f16::INFINITY, f16::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f32::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f32::INFINITY, f32::NEG_INFINITY) + ); + assert_eq!( + Complex::new(f64::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f64::INFINITY, f64::NEG_INFINITY) + ); + #[cfg(target_has_reliable_f128)] + assert_eq!( + Complex::new(f128::INFINITY, 0.0) / Complex::new(1.0, 1.0), + Complex::new(f128::INFINITY, f128::NEG_INFINITY) + ); +}