1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::f64::consts::PI;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub struct Torus {
9 major_radius: f64,
10 minor_radius: f64,
11}
12
13impl Torus {
14 #[must_use]
16 pub const fn new(major_radius: f64, minor_radius: f64) -> Option<Self> {
17 if major_radius.is_finite()
18 && minor_radius.is_finite()
19 && major_radius > 0.0
20 && minor_radius > 0.0
21 {
22 Some(Self {
23 major_radius,
24 minor_radius,
25 })
26 } else {
27 None
28 }
29 }
30
31 #[must_use]
33 pub const fn major_radius(self) -> f64 {
34 self.major_radius
35 }
36
37 #[must_use]
39 pub const fn minor_radius(self) -> f64 {
40 self.minor_radius
41 }
42
43 #[must_use]
45 pub fn surface_area(self) -> f64 {
46 4.0 * PI * PI * self.major_radius * self.minor_radius
47 }
48
49 #[must_use]
51 pub fn volume(self) -> f64 {
52 2.0 * PI * PI * self.major_radius * self.minor_radius * self.minor_radius
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use core::f64::consts::PI;
59
60 use super::Torus;
61
62 fn approx_eq(left: f64, right: f64) -> bool {
63 (left - right).abs() < 1.0e-10
64 }
65
66 #[test]
67 fn computes_torus_measurements() {
68 let torus = Torus::new(3.0, 1.0).expect("valid torus");
69
70 assert_eq!(torus.major_radius(), 3.0);
71 assert_eq!(torus.minor_radius(), 1.0);
72 assert!(approx_eq(torus.surface_area(), 12.0 * PI * PI));
73 assert!(approx_eq(torus.volume(), 6.0 * PI * PI));
74 assert_eq!(Torus::new(3.0, 0.0), None);
75 }
76}