use crate::Complex;
macro_rules! complex_ops {
($t:ty, $mul:ident, $recover_product:ident, $div:ident, $mul_real:ident, $real_mul:ident, $add_real:ident,
$real_add:ident, $sub_real:ident, $real_sub:ident, $div_real:ident, $real_div:ident,
$conj:ident, $proj:ident, $nonzero:ident, $eq:ident, $ne:ident, $cty:literal) => {
#[doc = $cty]
#[inline]
pub fn $mul(z: Complex<$t>, w: Complex<$t>) -> Complex<$t> {
let (a, b, c, d) = (z.re, z.im, w.re, w.im);
let (ac, bd, ad, bc) = (a * c, b * d, a * d, b * c);
let x = ac - bd;
let y = ad + bc;
if x.is_nan() && y.is_nan() {
return $recover_product(z, w, [ac, bd, ad, bc], x, y);
}
Complex { re: x, im: y }
}
#[cold]
#[inline(never)]
fn $recover_product(
z: Complex<$t>,
w: Complex<$t>,
products: [$t; 4],
x: $t,
y: $t,
) -> Complex<$t> {
let (mut a, mut b, mut c, mut d) = (z.re, z.im, w.re, w.im);
let [ac, bd, ad, bc] = products;
let mut recalc = false;
if a.is_infinite() || b.is_infinite() {
a = unit(a);
b = unit(b);
c = tame(c);
d = tame(d);
recalc = true;
}
if c.is_infinite() || d.is_infinite() {
c = unit(c);
d = unit(d);
a = tame(a);
b = tame(b);
recalc = true;
}
if !recalc
&& (ac.is_infinite() || bd.is_infinite() || ad.is_infinite() || bc.is_infinite())
{
a = tame(a);
b = tame(b);
c = tame(c);
d = tame(d);
recalc = true;
}
if recalc {
let inf = <$t>::INFINITY;
Complex {
re: inf * (a * c - b * d),
im: inf * (a * d + b * c),
}
} else {
Complex { re: x, im: y }
}
}
#[doc = $cty]
#[inline]
pub fn $mul_real(z: Complex<$t>, x: $t) -> Complex<$t> {
Complex {
re: z.re * x,
im: z.im * x,
}
}
#[doc = $cty]
#[inline]
pub fn $real_mul(x: $t, z: Complex<$t>) -> Complex<$t> {
$mul_real(z, x)
}
#[doc = $cty]
#[inline]
pub fn $add_real(z: Complex<$t>, x: $t) -> Complex<$t> {
Complex {
re: z.re + x,
im: z.im,
}
}
#[doc = $cty]
#[doc = stringify!($real_mul)]
#[inline]
pub fn $real_add(x: $t, z: Complex<$t>) -> Complex<$t> {
Complex {
re: x + z.re,
im: z.im,
}
}
#[doc = $cty]
#[inline]
pub fn $sub_real(z: Complex<$t>, x: $t) -> Complex<$t> {
Complex {
re: z.re - x,
im: z.im,
}
}
#[doc = $cty]
#[inline]
pub fn $real_sub(x: $t, z: Complex<$t>) -> Complex<$t> {
Complex {
re: x - z.re,
im: -z.im,
}
}
#[doc = $cty]
#[inline]
pub fn $div_real(z: Complex<$t>, x: $t) -> Complex<$t> {
Complex {
re: z.re / x,
im: z.im / x,
}
}
#[doc = $cty]
#[inline]
pub fn $real_div(x: $t, z: Complex<$t>) -> Complex<$t> {
$div(Complex { re: x, im: 0 as $t }, z)
}
#[doc = $cty]
#[inline]
pub fn $conj(z: Complex<$t>) -> Complex<$t> {
Complex {
re: z.re,
im: -z.im,
}
}
#[doc = $cty]
#[inline]
pub fn $proj(z: Complex<$t>) -> Complex<$t> {
if z.re.is_infinite() || z.im.is_infinite() {
Complex {
re: <$t>::INFINITY,
im: copysign(0 as $t, z.im),
}
} else {
z
}
}
#[doc = $cty]
#[inline]
pub fn $nonzero(z: Complex<$t>) -> bool {
z.re != 0 as $t || z.im != 0 as $t
}
#[doc = $cty]
#[inline]
pub fn $eq(z: Complex<$t>, w: Complex<$t>) -> bool {
z.re == w.re && z.im == w.im
}
#[doc = $cty]
#[doc = concat!("[`", stringify!($eq), "`]")]
#[inline]
pub fn $ne(z: Complex<$t>, w: Complex<$t>) -> bool {
!$eq(z, w)
}
};
}
complex_ops!(
f32,
mul_f32,
recover_product_f32,
div_f32,
mul_real_f32,
real_mul_f32,
add_real_f32,
real_add_f32,
sub_real_f32,
real_sub_f32,
div_real_f32,
real_div_f32,
conj_f32,
proj_f32,
nonzero_f32,
eq_f32,
ne_f32,
"`float _Complex`"
);
complex_ops!(
f64,
mul_f64,
recover_product_f64,
div_f64,
mul_real_f64,
real_mul_f64,
add_real_f64,
real_add_f64,
sub_real_f64,
real_sub_f64,
div_real_f64,
real_div_f64,
conj_f64,
proj_f64,
nonzero_f64,
eq_f64,
ne_f64,
"`double _Complex`"
);
#[inline]
pub fn div_f32(z: Complex<f32>, w: Complex<f32>) -> Complex<f32> {
let (a, b) = (f64::from(z.re), f64::from(z.im));
let (c, d) = (f64::from(w.re), f64::from(w.im));
let denom = c * c + d * d;
let (x, y) = recover_quotient(a, b, c, d, (a * c + b * d) / denom, (b * c - a * d) / denom);
Complex {
re: x as f32,
im: y as f32,
}
}
#[inline]
pub fn div_f64(z: Complex<f64>, w: Complex<f64>) -> Complex<f64> {
let (a, b, c, d) = (z.re, z.im, w.re, w.im);
let (x, y) = if abs(c) < abs(d) {
let ratio = c / d;
let denom = c * ratio + d;
if abs(ratio) > f64::MIN_POSITIVE {
((a * ratio + b) / denom, (b * ratio - a) / denom)
} else {
(((a / d) * c + b) / denom, ((b / d) * c - a) / denom)
}
} else {
let ratio = d / c;
let denom = d * ratio + c;
if abs(ratio) > f64::MIN_POSITIVE {
((b * ratio + a) / denom, (b - a * ratio) / denom)
} else {
(((b / c) * d + a) / denom, (b - (a / c) * d) / denom)
}
};
let (re, im) = recover_quotient(a, b, c, d, x, y);
Complex { re, im }
}
#[inline]
fn recover_quotient(a: f64, b: f64, c: f64, d: f64, x: f64, y: f64) -> (f64, f64) {
if x.is_nan() && y.is_nan() {
return recover_nan_quotient(a, b, c, d, x, y);
}
(x, y)
}
#[cold]
#[inline(never)]
fn recover_nan_quotient(
mut a: f64,
mut b: f64,
mut c: f64,
mut d: f64,
x: f64,
y: f64,
) -> (f64, f64) {
let inf = f64::INFINITY;
if c == 0.0 && d == 0.0 && (!a.is_nan() || !b.is_nan()) {
let scale = copysign(inf, c);
return (scale * a, scale * b);
}
if (a.is_infinite() || b.is_infinite()) && c.is_finite() && d.is_finite() {
a = unit(a);
b = unit(b);
return (inf * (a * c + b * d), inf * (b * c - a * d));
}
if (c.is_infinite() || d.is_infinite()) && a.is_finite() && b.is_finite() {
c = unit(c);
d = unit(d);
return (0.0 * (a * c + b * d), 0.0 * (b * c - a * d));
}
(x, y)
}
#[inline]
pub fn widen_f32(z: Complex<f32>) -> Complex<f64> {
Complex {
re: f64::from(z.re),
im: f64::from(z.im),
}
}
#[inline]
pub fn narrow_f64(z: Complex<f64>) -> Complex<f32> {
Complex {
re: z.re as f32,
im: z.im as f32,
}
}
trait Bits: Copy {
fn magnitude(self) -> Self;
fn with_sign(self, y: Self) -> Self;
}
macro_rules! bits {
($t:ty, $u:ty) => {
impl Bits for $t {
#[inline]
fn magnitude(self) -> Self {
const SIGN: $u = 1 << (<$u>::BITS - 1);
<$t>::from_bits(self.to_bits() & !SIGN)
}
#[inline]
fn with_sign(self, y: Self) -> Self {
const SIGN: $u = 1 << (<$u>::BITS - 1);
<$t>::from_bits((self.to_bits() & !SIGN) | (y.to_bits() & SIGN))
}
}
};
}
bits!(f32, u32);
bits!(f64, u64);
#[inline]
fn abs<T: Bits>(x: T) -> T {
x.magnitude()
}
#[inline]
fn copysign<T: Bits>(x: T, y: T) -> T {
x.with_sign(y)
}
#[inline]
fn unit<T: Bits + Float>(x: T) -> T {
copysign(if x.is_infinite() { T::ONE } else { T::ZERO }, x)
}
#[inline]
fn tame<T: Bits + Float>(x: T) -> T {
if x.is_nan() { copysign(T::ZERO, x) } else { x }
}
trait Float: Copy {
const ZERO: Self;
const ONE: Self;
fn is_infinite(self) -> bool;
fn is_nan(self) -> bool;
}
macro_rules! float {
($t:ty) => {
impl Float for $t {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
#[inline]
fn is_infinite(self) -> bool {
<$t>::is_infinite(self)
}
#[inline]
fn is_nan(self) -> bool {
<$t>::is_nan(self)
}
}
};
}
float!(f32);
float!(f64);
#[cfg(test)]
mod tests {
use super::*;
fn same(a: f64, b: f64) -> bool {
if a.is_nan() && b.is_nan() {
return true;
}
a == b && a.is_sign_negative() == b.is_sign_negative()
}
fn same_c(a: Complex<f64>, b: Complex<f64>) -> bool {
same(a.re, b.re) && same(a.im, b.im)
}
#[test]
fn the_ordinary_product_and_quotient_are_the_school_ones() {
let z = Complex::new(1.0, 2.0);
let w = Complex::new(3.0, -4.0);
assert_eq!(mul_f64(z, w), Complex::new(11.0, 2.0));
assert_eq!(div_f64(z, w), Complex::new(-0.2, 0.4));
}
#[test]
fn an_infinity_survives_a_naive_nan() {
let inf = f64::INFINITY;
assert!(same_c(
mul_f64(Complex::new(inf, 0.0), Complex::new(3.0, -4.0)),
Complex::new(inf, -inf)
));
assert!(same_c(
div_f64(Complex::new(inf, 0.0), Complex::new(3.0, -4.0)),
Complex::new(inf, inf)
));
assert!(same_c(
div_f64(Complex::new(3.0, -4.0), Complex::new(inf, 0.0)),
Complex::new(0.0, -0.0)
));
}
#[test]
fn division_by_zero_is_a_signed_infinity() {
let inf = f64::INFINITY;
assert!(same_c(
div_f64(Complex::new(1.0, 2.0), Complex::new(0.0, 0.0)),
Complex::new(inf, inf)
));
assert!(same_c(
div_f64(Complex::new(1.0, 2.0), Complex::new(-0.0, -0.0)),
Complex::new(-inf, -inf)
));
}
#[test]
fn a_real_operand_is_componentwise() {
let nzero = Complex::new(-0.0, -0.0);
assert!(same_c(mul_real_f64(nzero, 3.0), Complex::new(-0.0, -0.0)));
assert!(same_c(
real_sub_f64(0.0, Complex::new(1.0, 0.0)),
Complex::new(-1.0, -0.0)
));
assert!(same_c(
div_real_f64(Complex::new(1.0, 2.0), 0.0),
Complex::new(f64::INFINITY, f64::INFINITY)
));
}
#[test]
fn conjugation_projection_and_truth() {
let z = Complex::new(1.0, 2.0);
assert_eq!(conj_f64(z), Complex::new(1.0, -2.0));
assert_eq!(proj_f64(z), z);
assert!(same_c(
proj_f64(Complex::new(1.0, f64::NEG_INFINITY)),
Complex::new(f64::INFINITY, -0.0)
));
assert!(nonzero_f64(Complex::new(0.0, 1.0)));
assert!(!nonzero_f64(Complex::new(0.0, -0.0)));
assert!(nonzero_f64(Complex::new(f64::NAN, 0.0)));
}
#[test]
fn the_f32_forms_agree_with_the_f64_ones_on_exact_values() {
let z = Complex::new(1.0f32, 2.0);
let w = Complex::new(3.0f32, -4.0);
assert_eq!(mul_f32(z, w), Complex::new(11.0, 2.0));
assert_eq!(widen_f32(mul_f32(z, w)), Complex::new(11.0f64, 2.0));
assert_eq!(
narrow_f64(Complex::new(11.0f64, 2.0)),
Complex::new(11.0f32, 2.0)
);
}
}