Skip to main content

fits_well/
bitpix.rs

1use crate::error::FitsError;
2use crate::error::Result;
3
4/// The physical element type of an array, selected by the `BITPIX` keyword.
5///
6/// Note the asymmetry mandated by the standard: `BITPIX = 8` is the *only*
7/// natively unsigned integer; 16/32/64-bit are two's-complement signed. Other
8/// unsigned widths and signed bytes are faked via a `BZERO`/`TZERO` offset and
9/// are detected at the scaling layer, not here.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum Bitpix {
12    /// `8` — unsigned 8-bit integer (or raw character).
13    U8,
14    /// `16` — signed 16-bit integer.
15    I16,
16    /// `32` — signed 32-bit integer.
17    I32,
18    /// `64` — signed 64-bit integer.
19    I64,
20    /// `-32` — IEEE-754 single-precision float.
21    F32,
22    /// `-64` — IEEE-754 double-precision float.
23    F64,
24}
25
26impl Bitpix {
27    /// Parse the integer `BITPIX` keyword value.
28    pub fn from_code(code: i64) -> Result<Self> {
29        match code {
30            8 => Ok(Bitpix::U8),
31            16 => Ok(Bitpix::I16),
32            32 => Ok(Bitpix::I32),
33            64 => Ok(Bitpix::I64),
34            -32 => Ok(Bitpix::F32),
35            -64 => Ok(Bitpix::F64),
36            _ => Err(FitsError::InvalidBitpix { code }),
37        }
38    }
39
40    /// The integer `BITPIX` keyword value for this type.
41    pub fn code(self) -> i64 {
42        match self {
43            Bitpix::U8 => 8,
44            Bitpix::I16 => 16,
45            Bitpix::I32 => 32,
46            Bitpix::I64 => 64,
47            Bitpix::F32 => -32,
48            Bitpix::F64 => -64,
49        }
50    }
51
52    /// Size of a single element in bytes (`|BITPIX| / 8`).
53    pub fn elem_size(self) -> usize {
54        (self.code().unsigned_abs() / 8) as usize
55    }
56
57    pub fn is_float(self) -> bool {
58        matches!(self, Bitpix::F32 | Bitpix::F64)
59    }
60
61    pub fn is_integer(self) -> bool {
62        !self.is_float()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn code_round_trips_for_every_variant() {
72        for bp in [
73            Bitpix::U8,
74            Bitpix::I16,
75            Bitpix::I32,
76            Bitpix::I64,
77            Bitpix::F32,
78            Bitpix::F64,
79        ] {
80            assert_eq!(Bitpix::from_code(bp.code()).unwrap(), bp);
81        }
82    }
83
84    #[test]
85    fn codes_and_sizes_match_the_standard() {
86        // (BITPIX code, byte size, is_float)
87        let cases = [
88            (Bitpix::U8, 8, 1, false),
89            (Bitpix::I16, 16, 2, false),
90            (Bitpix::I32, 32, 4, false),
91            (Bitpix::I64, 64, 8, false),
92            (Bitpix::F32, -32, 4, true),
93            (Bitpix::F64, -64, 8, true),
94        ];
95        for (bp, code, size, is_float) in cases {
96            assert_eq!(bp.code(), code);
97            assert_eq!(bp.elem_size(), size);
98            assert_eq!(bp.is_float(), is_float);
99            assert_eq!(bp.is_integer(), !is_float);
100        }
101    }
102
103    #[test]
104    fn rejects_codes_outside_the_allowed_set() {
105        for bad in [0, 7, 1, -1, 24, 128, -16] {
106            assert!(matches!(
107                Bitpix::from_code(bad),
108                Err(FitsError::InvalidBitpix { code }) if code == bad
109            ));
110        }
111    }
112}