1use crate::options::FloatFormat;
5use copybook_core::{Error, ErrorCode, Result};
6
7const IBM_HEX_EXPONENT_BIAS: i32 = 64;
8const IBM_HEX_FRACTION_MIN: f64 = 1.0 / 16.0;
9const IBM_HEX_SINGLE_FRACTION_BITS: u32 = 24;
10const IBM_HEX_DOUBLE_FRACTION_BITS: u32 = 56;
11
12#[inline]
13fn validate_float_buffer_len(data: &[u8], required: usize, usage: &str) -> Result<()> {
14 if data.len() < required {
15 return Err(Error::new(
16 ErrorCode::CBKD301_RECORD_TOO_SHORT,
17 format!("{usage} requires {required} bytes, got {}", data.len()),
18 ));
19 }
20 Ok(())
21}
22
23#[inline]
24fn validate_float_encode_buffer_len(buffer: &[u8], required: usize, usage: &str) -> Result<()> {
25 if buffer.len() < required {
26 return Err(Error::new(
27 ErrorCode::CBKE510_NUMERIC_OVERFLOW,
28 format!(
29 "{usage} requires {required} bytes, buffer has {}",
30 buffer.len()
31 ),
32 ));
33 }
34 Ok(())
35}
36
37#[inline]
38#[allow(clippy::cast_precision_loss)]
39fn decode_ibm_hex_to_f64(sign: bool, exponent_raw: u8, fraction_bits: u64, bits: u32) -> f64 {
40 if exponent_raw == 0 && fraction_bits == 0 {
41 return if sign { -0.0 } else { 0.0 };
42 }
43
44 let exponent = i32::from(exponent_raw) - IBM_HEX_EXPONENT_BIAS;
45 let divisor = 2_f64.powi(i32::try_from(bits).unwrap_or(0));
46 let fraction = (fraction_bits as f64) / divisor;
47 let magnitude = fraction * 16_f64.powi(exponent);
48 if sign { -magnitude } else { magnitude }
49}
50
51#[inline]
52#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
53fn encode_f64_to_ibm_hex_parts(value: f64, bits: u32) -> Result<(u8, u64)> {
54 if !value.is_finite() {
55 return Err(Error::new(
56 ErrorCode::CBKE510_NUMERIC_OVERFLOW,
57 "IBM hex float encoding requires finite values",
58 ));
59 }
60
61 if value == 0.0 {
62 return Ok((0, 0));
63 }
64
65 let mut exponent = IBM_HEX_EXPONENT_BIAS;
66 let mut fraction = value.abs();
67
68 while fraction < IBM_HEX_FRACTION_MIN {
69 fraction *= 16.0;
70 exponent -= 1;
71 if exponent <= 0 {
72 return Ok((0, 0));
73 }
74 }
75
76 while fraction >= 1.0 {
77 fraction /= 16.0;
78 exponent += 1;
79 if exponent >= 128 {
80 return Err(Error::new(
81 ErrorCode::CBKE510_NUMERIC_OVERFLOW,
82 "IBM hex float exponent overflow",
83 ));
84 }
85 }
86
87 let scale = 2_f64.powi(i32::try_from(bits).unwrap_or(0));
88 let mut fraction_bits = (fraction * scale).round() as u64;
89 let full_scale = 1_u64 << bits;
90 if fraction_bits >= full_scale {
91 fraction_bits = 1_u64 << (bits - 4);
93 exponent += 1;
94 if exponent >= 128 {
95 return Err(Error::new(
96 ErrorCode::CBKE510_NUMERIC_OVERFLOW,
97 "IBM hex float exponent overflow",
98 ));
99 }
100 }
101
102 Ok((u8::try_from(exponent).unwrap_or(0), fraction_bits))
103}
104
105#[inline]
110#[must_use = "Handle the Result or propagate the error"]
111pub fn decode_float_single_ieee_be(data: &[u8]) -> Result<f32> {
112 validate_float_buffer_len(data, 4, "COMP-1")?;
113 let bytes: [u8; 4] = [data[0], data[1], data[2], data[3]];
114 Ok(f32::from_be_bytes(bytes))
115}
116
117#[inline]
122#[must_use = "Handle the Result or propagate the error"]
123pub fn decode_float_double_ieee_be(data: &[u8]) -> Result<f64> {
124 validate_float_buffer_len(data, 8, "COMP-2")?;
125 let bytes: [u8; 8] = [
126 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
127 ];
128 Ok(f64::from_be_bytes(bytes))
129}
130
131#[inline]
136#[must_use = "Handle the Result or propagate the error"]
137pub fn decode_float_single_ibm_hex(data: &[u8]) -> Result<f32> {
138 validate_float_buffer_len(data, 4, "COMP-1")?;
139 let word = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
140 let sign = (word & 0x8000_0000) != 0;
141 let exponent_raw = ((word >> 24) & 0x7F) as u8;
142 let fraction_bits = u64::from(word & 0x00FF_FFFF);
143 let value = decode_ibm_hex_to_f64(
144 sign,
145 exponent_raw,
146 fraction_bits,
147 IBM_HEX_SINGLE_FRACTION_BITS,
148 );
149 #[allow(clippy::cast_possible_truncation)]
150 {
151 Ok(value as f32)
152 }
153}
154
155#[inline]
160#[must_use = "Handle the Result or propagate the error"]
161pub fn decode_float_double_ibm_hex(data: &[u8]) -> Result<f64> {
162 validate_float_buffer_len(data, 8, "COMP-2")?;
163 let word = u64::from_be_bytes([
164 data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
165 ]);
166 let sign = (word & 0x8000_0000_0000_0000) != 0;
167 let exponent_raw = ((word >> 56) & 0x7F) as u8;
168 let fraction_bits = word & 0x00FF_FFFF_FFFF_FFFF;
169 Ok(decode_ibm_hex_to_f64(
170 sign,
171 exponent_raw,
172 fraction_bits,
173 IBM_HEX_DOUBLE_FRACTION_BITS,
174 ))
175}
176
177#[inline]
182#[must_use = "Handle the Result or propagate the error"]
183pub fn decode_float_single_with_format(data: &[u8], format: FloatFormat) -> Result<f32> {
184 match format {
185 FloatFormat::IeeeBigEndian => decode_float_single_ieee_be(data),
186 FloatFormat::IbmHex => decode_float_single_ibm_hex(data),
187 }
188}
189
190#[inline]
195#[must_use = "Handle the Result or propagate the error"]
196pub fn decode_float_double_with_format(data: &[u8], format: FloatFormat) -> Result<f64> {
197 match format {
198 FloatFormat::IeeeBigEndian => decode_float_double_ieee_be(data),
199 FloatFormat::IbmHex => decode_float_double_ibm_hex(data),
200 }
201}
202
203#[inline]
208#[must_use = "Handle the Result or propagate the error"]
209pub fn decode_float_single(data: &[u8]) -> Result<f32> {
210 decode_float_single_ieee_be(data)
211}
212
213#[inline]
218#[must_use = "Handle the Result or propagate the error"]
219pub fn decode_float_double(data: &[u8]) -> Result<f64> {
220 decode_float_double_ieee_be(data)
221}
222
223#[inline]
228#[must_use = "Handle the Result or propagate the error"]
229pub fn encode_float_single_ieee_be(value: f32, buffer: &mut [u8]) -> Result<()> {
230 validate_float_encode_buffer_len(buffer, 4, "COMP-1")?;
231 let bytes = value.to_be_bytes();
232 buffer[..4].copy_from_slice(&bytes);
233 Ok(())
234}
235
236#[inline]
241#[must_use = "Handle the Result or propagate the error"]
242pub fn encode_float_double_ieee_be(value: f64, buffer: &mut [u8]) -> Result<()> {
243 validate_float_encode_buffer_len(buffer, 8, "COMP-2")?;
244 let bytes = value.to_be_bytes();
245 buffer[..8].copy_from_slice(&bytes);
246 Ok(())
247}
248
249#[inline]
256#[must_use = "Handle the Result or propagate the error"]
257pub fn encode_float_single_ibm_hex(value: f32, buffer: &mut [u8]) -> Result<()> {
258 validate_float_encode_buffer_len(buffer, 4, "COMP-1")?;
259 let sign = value.is_sign_negative();
260 let (exponent_raw, fraction_bits) =
261 encode_f64_to_ibm_hex_parts(f64::from(value), IBM_HEX_SINGLE_FRACTION_BITS)?;
262 let sign_bit = if sign { 0x8000_0000 } else { 0 };
263 let fraction_low = u32::try_from(fraction_bits & 0x00FF_FFFF).map_err(|_| {
264 Error::new(
265 ErrorCode::CBKE510_NUMERIC_OVERFLOW,
266 "IBM hex fraction overflow for COMP-1",
267 )
268 })?;
269 let word = sign_bit | (u32::from(exponent_raw) << 24) | fraction_low;
270 buffer[..4].copy_from_slice(&word.to_be_bytes());
271 Ok(())
272}
273
274#[inline]
281#[must_use = "Handle the Result or propagate the error"]
282pub fn encode_float_double_ibm_hex(value: f64, buffer: &mut [u8]) -> Result<()> {
283 validate_float_encode_buffer_len(buffer, 8, "COMP-2")?;
284 let sign = value.is_sign_negative();
285 let (exponent_raw, fraction_bits) =
286 encode_f64_to_ibm_hex_parts(value, IBM_HEX_DOUBLE_FRACTION_BITS)?;
287 let sign_bit = if sign { 0x8000_0000_0000_0000 } else { 0 };
288 let word = sign_bit | (u64::from(exponent_raw) << 56) | (fraction_bits & 0x00FF_FFFF_FFFF_FFFF);
289 buffer[..8].copy_from_slice(&word.to_be_bytes());
290 Ok(())
291}
292
293#[inline]
298#[must_use = "Handle the Result or propagate the error"]
299pub fn encode_float_single_with_format(
300 value: f32,
301 buffer: &mut [u8],
302 format: FloatFormat,
303) -> Result<()> {
304 match format {
305 FloatFormat::IeeeBigEndian => encode_float_single_ieee_be(value, buffer),
306 FloatFormat::IbmHex => encode_float_single_ibm_hex(value, buffer),
307 }
308}
309
310#[inline]
315#[must_use = "Handle the Result or propagate the error"]
316pub fn encode_float_double_with_format(
317 value: f64,
318 buffer: &mut [u8],
319 format: FloatFormat,
320) -> Result<()> {
321 match format {
322 FloatFormat::IeeeBigEndian => encode_float_double_ieee_be(value, buffer),
323 FloatFormat::IbmHex => encode_float_double_ibm_hex(value, buffer),
324 }
325}
326
327#[inline]
332#[must_use = "Handle the Result or propagate the error"]
333pub fn encode_float_single(value: f32, buffer: &mut [u8]) -> Result<()> {
334 encode_float_single_ieee_be(value, buffer)
335}
336
337#[inline]
342#[must_use = "Handle the Result or propagate the error"]
343pub fn encode_float_double(value: f64, buffer: &mut [u8]) -> Result<()> {
344 encode_float_double_ieee_be(value, buffer)
345}