lofty/util/
math.rs

1/// Perform a rounded division.
2///
3/// This is implemented for all unsigned integers.
4///
5/// NOTE: If the result is less than 1, it will be rounded up to 1.
6pub(crate) trait RoundedDivision<Rhs = Self> {
7	type Output;
8
9	fn div_round(self, rhs: Rhs) -> Self::Output;
10}
11
12macro_rules! unsigned_rounded_division {
13	($($t:ty),*) => {
14		$(
15			impl RoundedDivision for $t {
16				type Output = $t;
17
18				fn div_round(self, rhs: Self) -> Self::Output {
19					(self + (rhs >> 1)) / rhs
20				}
21			}
22		)*
23	};
24}
25
26unsigned_rounded_division!(u8, u16, u32, u64, u128, usize);
27
28/// An 80-bit extended precision floating-point number.
29///
30/// This is used in AIFF.
31#[derive(Debug, Eq, PartialEq, Copy, Clone)]
32pub(crate) struct F80 {
33	signed: bool,
34	// 15-bit exponent with a bias of 16383
35	exponent: u16,
36	fraction: u64,
37}
38
39impl F80 {
40	/// Create a new `F80` from big-endian bytes.
41	///
42	/// See [here](https://en.wikipedia.org/wiki/Extended_precision#/media/File:X86_Extended_Floating_Point_Format.svg) for a diagram of the format.
43	pub fn from_be_bytes(bytes: [u8; 10]) -> Self {
44		let signed = bytes[0] & 0x80 != 0;
45		let exponent = (u16::from(bytes[0] & 0x7F) << 8) | u16::from(bytes[1]);
46
47		let mut fraction_bytes = [0; 8];
48		fraction_bytes.copy_from_slice(&bytes[2..]);
49		let fraction = u64::from_be_bytes(fraction_bytes);
50
51		Self {
52			signed,
53			exponent,
54			fraction,
55		}
56	}
57
58	/// Convert the `F80` to an `f64`.
59	pub fn as_f64(&self) -> f64 {
60		// AppleĀ® Apple Numerics Manual, Second Edition, Table 2-7:
61		//
62		// Biased exponent e  Integer i  Fraction f          Value v                     Class of v
63		// 0 <= e <= 32766       1         (any)     v = (-1)^s * 2^(e-16383) * (1.f)   Normalized
64		// 0 <= e <= 32766       0         f != 0    v = (-1)^s * 2^(e-16383) * (0.f)   Denormalized
65		// 0 <= e <= 32766       0         f = 0     v = (-1)^s * 0                     Zero
66		//    e = 32767        (any)       f = 0     v = (-1)^s * Infinity              Infinity
67		//    e = 32767        (any)       f != 0    v is a NaN                         NaN
68
69		let sign = if self.signed { 1 } else { 0 };
70
71		// e = 32767
72		if self.exponent == 32767 {
73			if self.fraction == 0 {
74				return f64::from_bits((sign << 63) | f64::INFINITY.to_bits());
75			}
76
77			return f64::from_bits((sign << 63) | f64::NAN.to_bits());
78		}
79
80		// 0 <= e <= 32766, i = 0, f = 0
81		if self.fraction == 0 {
82			return f64::from_bits(sign << 63);
83		}
84
85		// 0 <= e <= 32766, 0 <= i <= 1, f >= 0
86
87		let fraction = self.fraction & 0x7FFF_FFFF_FFFF_FFFF;
88		let exponent = self.exponent as i16 - 16383 + 1023;
89		let bits = (sign << 63) | ((exponent as u64) << 52) | (fraction >> 11);
90
91		f64::from_bits(bits)
92	}
93}
94
95#[cfg(test)]
96mod tests {
97	use super::*;
98
99	#[test_log::test]
100	fn test_div_round() {
101		#[derive(Debug)]
102		struct TestEntry {
103			lhs: u32,
104			rhs: u32,
105			result: u32,
106		}
107
108		#[rustfmt::skip]
109		let tests = [
110			TestEntry { lhs: 1, rhs: 1, result: 1 },
111			TestEntry { lhs: 1, rhs: 2, result: 1 },
112			TestEntry { lhs: 2, rhs: 2, result: 1 },
113			TestEntry { lhs: 3, rhs: 2, result: 2 },
114			TestEntry { lhs: 4, rhs: 2, result: 2 },
115			TestEntry { lhs: 5, rhs: 2, result: 3 },
116
117			// Should be rounded up to 1
118			TestEntry { lhs: 800, rhs: 1500, result: 1 },
119			TestEntry { lhs: 1500, rhs: 3000, result: 1 },
120
121			// Shouldn't be rounded
122			TestEntry { lhs: 0, rhs: 4000, result: 0 },
123			TestEntry { lhs: 1500, rhs: 4000, result: 0 },
124		];
125
126		for test in &tests {
127			let result = test.lhs.div_round(test.rhs);
128			assert_eq!(result, test.result, "{}.div_round({})", test.lhs, test.rhs);
129		}
130	}
131
132	#[test_log::test]
133	fn test_f80() {
134		fn cmp_float_nearly_equal(a: f64, b: f64) -> bool {
135			if a.is_infinite() && b.is_infinite() {
136				return true;
137			}
138
139			if a.is_nan() && b.is_nan() {
140				return true;
141			}
142
143			(a - b).abs() < f64::EPSILON
144		}
145
146		#[derive(Debug)]
147		struct TestEntry {
148			input: [u8; 10],
149			output_f64: f64,
150		}
151
152		let tests = [
153			TestEntry {
154				input: [0; 10],
155				output_f64: 0.0,
156			},
157			TestEntry {
158				input: [0x7F, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0],
159				output_f64: f64::INFINITY,
160			},
161			TestEntry {
162				input: [0xFF, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0],
163				output_f64: f64::NEG_INFINITY,
164			},
165			TestEntry {
166				input: [0x7F, 0xFF, 0x80, 0, 0, 0, 0, 0, 0, 0],
167				output_f64: f64::NAN,
168			},
169			TestEntry {
170				input: [0xFF, 0xFF, 0x80, 0, 0, 0, 0, 0, 0, 0],
171				output_f64: -f64::NAN,
172			},
173			TestEntry {
174				input: [0x3F, 0xFC, 0x80, 0, 0, 0, 0, 0, 0, 0],
175				output_f64: 0.125,
176			},
177			TestEntry {
178				input: [0x3F, 0xFF, 0x80, 0, 0, 0, 0, 0, 0, 0],
179				output_f64: 1.0,
180			},
181			TestEntry {
182				input: [0x40, 0x00, 0x80, 0, 0, 0, 0, 0, 0, 0],
183				output_f64: 2.0,
184			},
185			TestEntry {
186				input: [0x40, 0x00, 0xC0, 0, 0, 0, 0, 0, 0, 0],
187				output_f64: 3.0,
188			},
189			TestEntry {
190				input: [0x40, 0x0E, 0xBB, 0x80, 0, 0, 0, 0, 0, 0],
191				output_f64: 48000.0,
192			},
193		];
194
195		for test in &tests {
196			let f80 = F80::from_be_bytes(test.input);
197			let f64 = f80.as_f64();
198			assert!(
199				cmp_float_nearly_equal(f64, test.output_f64),
200				"F80::as_f64({f80:?}) == {f64} (expected {})",
201				test.output_f64
202			);
203		}
204	}
205}