Skip to content

Commit 70fc9cb

Browse files
bors[bot]cuviper
andcommitted
61: Add `Complex::cbrt` for `T: Float` r=cuviper a=cuviper This also adds similar `sqrt` optimizations for imaginaries. Co-authored-by: Josh Stone <cuviper@gmail.com>
2 parents 41f2994 + a81fd0a commit 70fc9cb

1 file changed

Lines changed: 163 additions & 10 deletions

File tree

src/lib.rs

Lines changed: 163 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,21 +224,90 @@ impl<T: Clone + Float> Complex<T> {
224224
if self.re.is_sign_positive() {
225225
// simple positive real √r, and copy `im` for its sign
226226
Self::new(self.re.sqrt(), self.im)
227-
} else if self.im.is_sign_positive() {
228-
// √(r e^(iπ)) = √r e^(iπ/2) = i√r
229-
Self::new(T::zero(), self.re.abs().sqrt())
230227
} else {
228+
// √(r e^(iπ)) = √r e^(iπ/2) = i√r
231229
// √(r e^(-iπ)) = √r e^(-iπ/2) = -i√r
232-
Self::new(T::zero(), -self.re.abs().sqrt())
230+
let re = T::zero();
231+
let im = (-self.re).sqrt();
232+
if self.im.is_sign_positive() {
233+
Self::new(re, im)
234+
} else {
235+
Self::new(re, -im)
236+
}
237+
}
238+
} else if self.re.is_zero() {
239+
// √(r e^(iπ/2)) = √r e^(iπ/4) = √(r/2) + i√(r/2)
240+
// √(r e^(-iπ/2)) = √r e^(-iπ/4) = √(r/2) - i√(r/2)
241+
let one = T::one();
242+
let two = one + one;
243+
let x = (self.im.abs() / two).sqrt();
244+
if self.im.is_sign_positive() {
245+
Self::new(x, x)
246+
} else {
247+
Self::new(x, -x)
233248
}
234249
} else {
235250
// formula: sqrt(r e^(it)) = sqrt(r) e^(it/2)
236-
let two = T::one() + T::one();
251+
let one = T::one();
252+
let two = one + one;
237253
let (r, theta) = self.to_polar();
238254
Self::from_polar(&(r.sqrt()), &(theta / two))
239255
}
240256
}
241257

258+
/// Computes the principal value of the cube root of `self`.
259+
///
260+
/// This function has one branch cut:
261+
///
262+
/// * `(-∞, 0)`, continuous from above.
263+
///
264+
/// The branch satisfies `-π/3 ≤ arg(cbrt(z)) ≤ π/3`.
265+
///
266+
/// Note that this does not match the usual result for the cube root of
267+
/// negative real numbers. For example, the real cube root of `-8` is `-2`,
268+
/// but the principal complex cube root of `-8` is `1 + i√3`.
269+
#[inline]
270+
pub fn cbrt(&self) -> Self {
271+
if self.im.is_zero() {
272+
if self.re.is_sign_positive() {
273+
// simple positive real ∛r, and copy `im` for its sign
274+
Self::new(self.re.cbrt(), self.im)
275+
} else {
276+
// ∛(r e^(iπ)) = ∛r e^(iπ/3) = ∛r/2 + i∛r√3/2
277+
// ∛(r e^(-iπ)) = ∛r e^(-iπ/3) = ∛r/2 - i∛r√3/2
278+
let one = T::one();
279+
let two = one + one;
280+
let three = two + one;
281+
let re = (-self.re).cbrt() / two;
282+
let im = three.sqrt() * re;
283+
if self.im.is_sign_positive() {
284+
Self::new(re, im)
285+
} else {
286+
Self::new(re, -im)
287+
}
288+
}
289+
} else if self.re.is_zero() {
290+
// ∛(r e^(iπ/2)) = ∛r e^(iπ/6) = ∛r√3/2 + i∛r/2
291+
// ∛(r e^(-iπ/2)) = ∛r e^(-iπ/6) = ∛r√3/2 - i∛r/2
292+
let one = T::one();
293+
let two = one + one;
294+
let three = two + one;
295+
let im = self.im.abs().cbrt() / two;
296+
let re = three.sqrt() * im;
297+
if self.im.is_sign_positive() {
298+
Self::new(re, im)
299+
} else {
300+
Self::new(re, -im)
301+
}
302+
} else {
303+
// formula: cbrt(r e^(it)) = cbrt(r) e^(it/3)
304+
let one = T::one();
305+
let three = one + one + one;
306+
let (r, theta) = self.to_polar();
307+
Self::from_polar(&(r.cbrt()), &(theta / three))
308+
}
309+
}
310+
242311
/// Raises `self` to a floating point power.
243312
#[inline]
244313
pub fn powf(&self, exp: T) -> Self {
@@ -1695,8 +1764,8 @@ mod test {
16951764
assert!(close(c.conj().sqrt(), c.sqrt().conj()));
16961765
// for this branch, -pi/2 <= arg(sqrt(z)) <= pi/2
16971766
assert!(
1698-
-f64::consts::PI / 2.0 <= c.sqrt().arg()
1699-
&& c.sqrt().arg() <= f64::consts::PI / 2.0
1767+
-f64::consts::FRAC_PI_2 <= c.sqrt().arg()
1768+
&& c.sqrt().arg() <= f64::consts::FRAC_PI_2
17001769
);
17011770
// sqrt(z) * sqrt(z) = z
17021771
assert!(close(c.sqrt() * c.sqrt(), c));
@@ -1707,11 +1776,95 @@ mod test {
17071776
fn test_sqrt_real() {
17081777
for n in (0..100).map(f64::from) {
17091778
// √(n² + 0i) = n + 0i
1710-
assert_eq!(Complex64::new(n * n, 0.0).sqrt(), Complex64::new(n, 0.0));
1779+
let n2 = n * n;
1780+
assert_eq!(Complex64::new(n2, 0.0).sqrt(), Complex64::new(n, 0.0));
17111781
// √(-n² + 0i) = 0 + ni
1712-
assert_eq!(Complex64::new(-n * n, 0.0).sqrt(), Complex64::new(0.0, n));
1782+
assert_eq!(Complex64::new(-n2, 0.0).sqrt(), Complex64::new(0.0, n));
17131783
// √(-n² - 0i) = 0 - ni
1714-
assert_eq!(Complex64::new(-n * n, -0.0).sqrt(), Complex64::new(0.0, -n));
1784+
assert_eq!(Complex64::new(-n2, -0.0).sqrt(), Complex64::new(0.0, -n));
1785+
}
1786+
}
1787+
1788+
#[test]
1789+
fn test_sqrt_imag() {
1790+
for n in (0..100).map(f64::from) {
1791+
// √(0 + n²i) = n e^(iπ/4)
1792+
let n2 = n * n;
1793+
assert!(close(
1794+
Complex64::new(0.0, n2).sqrt(),
1795+
Complex64::from_polar(&n, &(f64::consts::FRAC_PI_4))
1796+
));
1797+
// √(0 - n²i) = n e^(-iπ/4)
1798+
assert!(close(
1799+
Complex64::new(0.0, -n2).sqrt(),
1800+
Complex64::from_polar(&n, &(-f64::consts::FRAC_PI_4))
1801+
));
1802+
}
1803+
}
1804+
1805+
#[test]
1806+
fn test_cbrt() {
1807+
assert!(close(_0_0i.cbrt(), _0_0i));
1808+
assert!(close(_1_0i.cbrt(), _1_0i));
1809+
assert!(close(
1810+
Complex::new(-1.0, 0.0).cbrt(),
1811+
Complex::new(0.5, 0.75.sqrt())
1812+
));
1813+
assert!(close(
1814+
Complex::new(-1.0, -0.0).cbrt(),
1815+
Complex::new(0.5, -0.75.sqrt())
1816+
));
1817+
assert!(close(_0_1i.cbrt(), Complex::new(0.75.sqrt(), 0.5)));
1818+
assert!(close(_0_1i.conj().cbrt(), Complex::new(0.75.sqrt(), -0.5)));
1819+
for &c in all_consts.iter() {
1820+
// cbrt(conj(z() = conj(cbrt(z))
1821+
assert!(close(c.conj().cbrt(), c.cbrt().conj()));
1822+
// for this branch, -pi/3 <= arg(cbrt(z)) <= pi/3
1823+
assert!(
1824+
-f64::consts::FRAC_PI_3 <= c.cbrt().arg()
1825+
&& c.cbrt().arg() <= f64::consts::FRAC_PI_3
1826+
);
1827+
// cbrt(z) * cbrt(z) cbrt(z) = z
1828+
assert!(close(c.cbrt() * c.cbrt() * c.cbrt(), c));
1829+
}
1830+
}
1831+
1832+
#[test]
1833+
fn test_cbrt_real() {
1834+
for n in (0..100).map(f64::from) {
1835+
// ∛(n³ + 0i) = n + 0i
1836+
let n3 = n * n * n;
1837+
assert!(close(
1838+
Complex64::new(n3, 0.0).cbrt(),
1839+
Complex64::new(n, 0.0)
1840+
));
1841+
// ∛(-n³ + 0i) = n e^(iπ/3)
1842+
assert!(close(
1843+
Complex64::new(-n3, 0.0).cbrt(),
1844+
Complex64::from_polar(&n, &(f64::consts::FRAC_PI_3))
1845+
));
1846+
// ∛(-n³ - 0i) = n e^(-iπ/3)
1847+
assert!(close(
1848+
Complex64::new(-n3, -0.0).cbrt(),
1849+
Complex64::from_polar(&n, &(-f64::consts::FRAC_PI_3))
1850+
));
1851+
}
1852+
}
1853+
1854+
#[test]
1855+
fn test_cbrt_imag() {
1856+
for n in (0..100).map(f64::from) {
1857+
// ∛(0 + n³i) = n e^(iπ/6)
1858+
let n3 = n * n * n;
1859+
assert!(close(
1860+
Complex64::new(0.0, n3).cbrt(),
1861+
Complex64::from_polar(&n, &(f64::consts::FRAC_PI_6))
1862+
));
1863+
// ∛(0 - n³i) = n e^(-iπ/6)
1864+
assert!(close(
1865+
Complex64::new(0.0, -n3).cbrt(),
1866+
Complex64::from_polar(&n, &(-f64::consts::FRAC_PI_6))
1867+
));
17151868
}
17161869
}
17171870

0 commit comments

Comments
 (0)