oxicuda_quantum/statevec/
fp16.rs1use num_complex::Complex;
22
23use crate::error::{QuantumError, QuantumResult};
24use crate::statevec::state::StateVector;
25
26type Complex32 = Complex<f32>;
27
28#[must_use]
32pub fn f32_to_f16(value: f32) -> u16 {
33 let bits = value.to_bits();
34 let sign = ((bits >> 16) & 0x8000) as u16;
35 let exp = ((bits >> 23) & 0xff) as i32;
37 let mant = bits & 0x007f_ffff;
38
39 if exp == 0xff {
40 return if mant != 0 {
42 sign | 0x7e00 } else {
44 sign | 0x7c00 };
46 }
47
48 let new_exp = exp - 127 + 15;
50
51 if new_exp >= 0x1f {
52 return sign | 0x7c00;
54 }
55 if new_exp <= 0 {
56 if new_exp < -10 {
58 return sign;
60 }
61 let mant_with_implicit = mant | 0x0080_0000;
63 let shift = (14 - new_exp) as u32; let mut half_mant = mant_with_implicit >> shift;
65 let remainder = mant_with_implicit & ((1 << shift) - 1);
67 let halfway = 1u32 << (shift - 1);
68 if remainder > halfway || (remainder == halfway && (half_mant & 1) == 1) {
69 half_mant += 1;
70 }
71 return sign | (half_mant as u16);
72 }
73
74 let mut half_mant = (mant >> 13) as u16;
76 let remainder = mant & 0x0000_1fff;
77 let halfway = 0x0000_1000u32;
78 let mut he = new_exp as u16;
79 if remainder > halfway || (remainder == halfway && (half_mant & 1) == 1) {
80 half_mant += 1;
81 if half_mant == 0x400 {
82 half_mant = 0;
84 he += 1;
85 if he >= 0x1f {
86 return sign | 0x7c00; }
88 }
89 }
90 sign | (he << 10) | half_mant
91}
92
93#[must_use]
95pub fn f16_to_f32(h: u16) -> f32 {
96 let sign = ((h & 0x8000) as u32) << 16;
97 let exp = ((h >> 10) & 0x1f) as u32;
98 let mant = (h & 0x03ff) as u32;
99
100 if exp == 0 {
101 if mant == 0 {
102 return f32::from_bits(sign);
104 }
105 let mut e = -1i32;
107 let mut m = mant;
108 while (m & 0x0400) == 0 {
109 m <<= 1;
110 e -= 1;
111 }
112 m &= 0x03ff; let f32_exp = ((e + 1 - 15 + 127) as u32) << 23;
114 let f32_mant = m << 13;
115 return f32::from_bits(sign | f32_exp | f32_mant);
116 }
117 if exp == 0x1f {
118 let f32_mant = mant << 13;
120 return f32::from_bits(sign | 0x7f80_0000 | f32_mant);
121 }
122 let f32_exp = (exp + 127 - 15) << 23;
124 let f32_mant = mant << 13;
125 f32::from_bits(sign | f32_exp | f32_mant)
126}
127
128#[must_use]
132pub fn f32_to_bf16(value: f32) -> u16 {
133 let bits = value.to_bits();
134 if value.is_nan() {
135 return ((bits >> 16) as u16) | 0x0040;
137 }
138 let lsb = (bits >> 16) & 1;
140 let rounding_bias = 0x7fff + lsb;
141 let rounded = bits.wrapping_add(rounding_bias);
142 (rounded >> 16) as u16
143}
144
145#[must_use]
147pub fn bf16_to_f32(b: u16) -> f32 {
148 f32::from_bits((b as u32) << 16)
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum HalfFormat {
154 Ieee,
156 Bfloat,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub struct HalfComplex {
163 pub re: u16,
164 pub im: u16,
165}
166
167#[derive(Debug, Clone)]
172pub struct HalfStateVector {
173 pub amps: Vec<HalfComplex>,
174 pub n_qubits: usize,
175 pub format: HalfFormat,
176}
177
178impl HalfStateVector {
179 #[must_use]
181 pub fn from_dense(sv: &StateVector, format: HalfFormat) -> Self {
182 let amps = sv.amps.iter().map(|a| pack(*a, format)).collect::<Vec<_>>();
183 Self {
184 amps,
185 n_qubits: sv.n_qubits,
186 format,
187 }
188 }
189
190 pub fn to_dense(&self) -> QuantumResult<StateVector> {
195 if self.n_qubits == 0 || self.n_qubits > 30 {
196 return Err(QuantumError::InvalidQubitCount { n: self.n_qubits });
197 }
198 let amps = self
199 .amps
200 .iter()
201 .map(|h| unpack(*h, self.format))
202 .collect::<Vec<_>>();
203 Ok(StateVector {
204 amps,
205 n_qubits: self.n_qubits,
206 })
207 }
208
209 #[must_use]
211 pub fn bytes(&self) -> usize {
212 self.amps.len() * std::mem::size_of::<HalfComplex>()
213 }
214}
215
216fn pack(z: Complex32, format: HalfFormat) -> HalfComplex {
217 match format {
218 HalfFormat::Ieee => HalfComplex {
219 re: f32_to_f16(z.re),
220 im: f32_to_f16(z.im),
221 },
222 HalfFormat::Bfloat => HalfComplex {
223 re: f32_to_bf16(z.re),
224 im: f32_to_bf16(z.im),
225 },
226 }
227}
228
229fn unpack(h: HalfComplex, format: HalfFormat) -> Complex32 {
230 match format {
231 HalfFormat::Ieee => Complex32::new(f16_to_f32(h.re), f16_to_f32(h.im)),
232 HalfFormat::Bfloat => Complex32::new(bf16_to_f32(h.re), bf16_to_f32(h.im)),
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn f16_exact_powers_of_two_roundtrip() {
242 for &v in &[0.0_f32, 1.0, 2.0, 0.5, -1.0, -0.25, 4.0, 0.125] {
243 let back = f16_to_f32(f32_to_f16(v));
244 assert!((back - v).abs() < 1e-6, "v={v} back={back}");
245 }
246 }
247
248 #[test]
249 fn f16_known_bit_patterns() {
250 assert_eq!(f32_to_f16(1.0), 0x3c00);
252 assert_eq!(f32_to_f16(-2.0), 0xc000);
253 assert_eq!(f32_to_f16(0.0), 0x0000);
254 assert!((f16_to_f32(0x3c00) - 1.0).abs() < 1e-9);
255 assert!((f16_to_f32(0x4000) - 2.0).abs() < 1e-9);
256 }
257
258 #[test]
259 fn f16_overflow_to_inf() {
260 let h = f32_to_f16(1.0e30);
261 assert!(f16_to_f32(h).is_infinite());
262 }
263
264 #[test]
265 fn f16_subnormal_roundtrip_small() {
266 let tiny = 6.0e-8_f32;
268 let back = f16_to_f32(f32_to_f16(tiny));
269 assert!(back > 0.0 && back < 1e-6, "back={back}");
270 }
271
272 #[test]
273 fn f16_rounds_to_nearest() {
274 let v = 1.0_f32 / 3.0;
276 let back = f16_to_f32(f32_to_f16(v));
277 assert!((back - v).abs() < 5e-4, "v={v} back={back}");
278 }
279
280 #[test]
281 fn bf16_roundtrip_keeps_exponent_range() {
282 for &v in &[1.0_f32, -1.0, 1.0e20, 1.0e-20, 0.0, 123.5, -0.001] {
283 let back = bf16_to_f32(f32_to_bf16(v));
284 if v == 0.0 {
286 assert_eq!(back, 0.0);
287 } else {
288 let rel = ((back - v) / v).abs();
289 assert!(rel < 0.01, "v={v} back={back} rel={rel}");
290 }
291 }
292 }
293
294 #[test]
295 fn bf16_is_truncated_top_half() {
296 assert_eq!(f32_to_bf16(1.0), 0x3f80);
298 assert!((bf16_to_f32(0x3f80) - 1.0).abs() < 1e-9);
299 }
300
301 #[test]
302 fn state_vector_pack_unpack_ieee() {
303 let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
304 let sv = StateVector {
305 amps: vec![
306 Complex32::new(inv_sqrt2, 0.0),
307 Complex32::new(0.0, inv_sqrt2),
308 ],
309 n_qubits: 1,
310 };
311 let half = HalfStateVector::from_dense(&sv, HalfFormat::Ieee);
312 let back = half.to_dense().expect("dense");
313 for (a, b) in back.amps.iter().zip(sv.amps.iter()) {
314 assert!((a.re - b.re).abs() < 1e-3, "re {a:?} vs {b:?}");
315 assert!((a.im - b.im).abs() < 1e-3, "im {a:?} vs {b:?}");
316 }
317 assert_eq!(half.bytes(), 8);
319 }
320
321 #[test]
322 fn state_vector_pack_unpack_bf16_preserves_norm() {
323 use crate::handle::LcgRng;
324 let mut rng = LcgRng::new(2024);
325 let mut amps = Vec::with_capacity(8);
326 for _ in 0..8 {
327 amps.push(Complex32::new(rng.next_normal(), rng.next_normal()));
328 }
329 let mut sv = StateVector { amps, n_qubits: 3 };
330 sv.normalize_inplace();
331 let half = HalfStateVector::from_dense(&sv, HalfFormat::Bfloat);
332 let back = half.to_dense().expect("dense");
333 let norm: f32 = back.amps.iter().map(|a| a.norm_sqr()).sum();
334 assert!((norm - 1.0).abs() < 5e-2, "norm={norm}");
336 }
337}