Skip to main content

zenith_float_num/
binfmt.rs

1//! Compact binary interchange for [`ExactNum`] and [`ExactNumArray`].
2//!
3//! The 16-byte inline record is target-independent: two big-endian `u32`
4//! mantissa words (64 bits), matching `INLINE_WORDS = 2` on a 32-bit `Word`.
5//! Wider mantissas use a heap record with the same `u32` limb unit so a
6//! 32-bit reader and a 64-bit writer agree. All multi-byte fields are
7//! network byte order.
8
9use crate::ieee_soft::ExactNumArray;
10use crate::ExactNum;
11use crate::Exponent;
12use crate::Sign;
13use crate::Word;
14use crate::INF_NEG;
15use crate::INF_POS;
16use crate::WORD_BIT_SIZE;
17use crate::{Error, NAN};
18use alloc::vec::Vec;
19
20/// First-byte version stored in every record.
21pub const BINARY_FORMAT_VERSION: u8 = 1;
22
23/// Bytes in [`InlineBinaryBuffer`] / [`ExactNum::to_inline_bytes`].
24pub const BINARY_INLINE_LEN: usize = 16;
25
26/// Mantissa bits that fit in the inline record (`2 × 32`).
27pub const BINARY_INLINE_MANT_BITS: usize = 64;
28
29/// `u32` limbs in the inline mantissa field.
30pub const BINARY_INLINE_U32_WORDS: usize = 2;
31
32/// Header bytes of a heap or array record (same size as the inline record).
33pub const BINARY_HEADER_LEN: usize = 16;
34
35/// Maximum `u32` limbs accepted by [`ExactNum::from_bytes`].
36pub const BINARY_MAX_U32: usize = 65_536;
37
38/// Maximum array elements accepted by [`ExactNumArray::from_bytes`].
39pub const BINARY_MAX_ELEMS: usize = 1_048_576;
40
41/// Positive finite (inline).
42pub const BINARY_FLAG_POS: u8 = 0x00;
43/// Negative finite (inline).
44pub const BINARY_FLAG_NEG: u8 = 0x01;
45/// [`INF_POS`].
46pub const BINARY_FLAG_INF_POS: u8 = 0x02;
47/// [`INF_NEG`].
48pub const BINARY_FLAG_INF_NEG: u8 = 0x03;
49/// NaN: [`Error::DivisionByZero`].
50pub const BINARY_FLAG_NAN_DIV0: u8 = 0x04;
51/// NaN: [`Error::InvalidArgument`].
52pub const BINARY_FLAG_NAN_INVALID: u8 = 0x05;
53/// NaN: [`Error::PrecisionRetryExhausted`].
54pub const BINARY_FLAG_NAN_RETRY: u8 = 0x06;
55/// NaN: [`Error::MemoryAllocation`].
56pub const BINARY_FLAG_NAN_OOM: u8 = 0x07;
57/// NaN: [`Error::ExponentOverflow`] with [`Sign::Pos`].
58pub const BINARY_FLAG_NAN_OVF_POS: u8 = 0x08;
59/// NaN: [`Error::ExponentOverflow`] with [`Sign::Neg`].
60pub const BINARY_FLAG_NAN_OVF_NEG: u8 = 0x09;
61/// NaN with no associated [`Error`].
62pub const BINARY_FLAG_NAN_BARE: u8 = 0x0A;
63/// Heap finite, positive.
64pub const BINARY_FLAG_HEAP_POS: u8 = 0x10;
65/// Heap finite, negative.
66pub const BINARY_FLAG_HEAP_NEG: u8 = 0x11;
67/// [`ExactNumArray`] record.
68pub const BINARY_FLAG_ARRAY: u8 = 0x20;
69
70const U32_BYTES: usize = 4;
71const WORD_U32: usize = WORD_BIT_SIZE / 32;
72
73/// Fixed 16-byte stack buffer for an inlined [`ExactNum`].
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct InlineBinaryBuffer {
76    bytes: [u8; BINARY_INLINE_LEN],
77}
78
79impl InlineBinaryBuffer {
80    /// The 16 encoded bytes.
81    pub fn as_bytes(&self) -> &[u8; BINARY_INLINE_LEN] {
82        &self.bytes
83    }
84
85    /// Consume and return the 16 encoded bytes.
86    pub fn into_bytes(self) -> [u8; BINARY_INLINE_LEN] {
87        self.bytes
88    }
89}
90
91impl ExactNum {
92    /// Encode an inlined value into a stack buffer.
93    ///
94    /// Specials always succeed. A finite mantissa wider than
95    /// [`BINARY_INLINE_MANT_BITS`] returns [`Error::MemoryAllocation`].
96    pub fn to_inline_bytes(&self) -> Result<InlineBinaryBuffer, Error> {
97        let mut bytes = [0u8; BINARY_INLINE_LEN];
98        write_inline(self, &mut bytes)?;
99        Ok(InlineBinaryBuffer { bytes })
100    }
101
102    /// Write the 16-byte inline record into `dest`.
103    ///
104    /// Returns [`Error::InvalidArgument`] if `dest` is shorter than
105    /// [`BINARY_INLINE_LEN`]. Wider finite mantissas return
106    /// [`Error::MemoryAllocation`].
107    pub fn write_inline_bytes(&self, dest: &mut [u8]) -> Result<usize, Error> {
108        if dest.len() < BINARY_INLINE_LEN {
109            return Err(Error::InvalidArgument);
110        }
111        write_inline(self, dest)?;
112        Ok(BINARY_INLINE_LEN)
113    }
114
115    /// Decode a 16-byte inline record.
116    pub fn from_inline_bytes(bytes: &[u8; BINARY_INLINE_LEN]) -> Result<Self, Error> {
117        let (v, n) = decode_num(bytes)?;
118        if n != BINARY_INLINE_LEN {
119            return Err(Error::InvalidArgument);
120        }
121        Ok(v)
122    }
123
124    /// Encode `self` (inline 16 bytes, or a heap record if the mantissa is wider).
125    pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
126        let n = encoded_len(self)?;
127        let mut out = Vec::new();
128        out.try_reserve_exact(n)?;
129        out.resize(n, 0);
130        let wrote = write_num(self, &mut out)?;
131        if wrote != n {
132            return Err(Error::InvalidArgument);
133        }
134        Ok(out)
135    }
136
137    /// Write the compact record into `dest` without allocating.
138    pub fn write_bytes(&self, dest: &mut [u8]) -> Result<usize, Error> {
139        write_num(self, dest)
140    }
141
142    /// Decode a compact record. Extra trailing bytes are [`Error::InvalidArgument`].
143    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
144        let (v, n) = decode_num(bytes)?;
145        if n != bytes.len() {
146            return Err(Error::InvalidArgument);
147        }
148        Ok(v)
149    }
150}
151
152impl ExactNumArray {
153    /// Encode shape, shared `p`, and every element.
154    pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
155        let n = encoded_array_len(self)?;
156        let mut out = Vec::new();
157        out.try_reserve_exact(n)?;
158        out.resize(n, 0);
159        let wrote = write_array(self, &mut out)?;
160        if wrote != n {
161            return Err(Error::InvalidArgument);
162        }
163        Ok(out)
164    }
165
166    /// Write the array record into `dest` without allocating.
167    pub fn write_bytes(&self, dest: &mut [u8]) -> Result<usize, Error> {
168        write_array(self, dest)
169    }
170
171    /// Decode an array record. Shape product must match the element count.
172    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
173        decode_array(bytes)
174    }
175}
176
177fn flag_nan(err: Option<Error>) -> u8 {
178    match err {
179        Some(Error::DivisionByZero) => BINARY_FLAG_NAN_DIV0,
180        Some(Error::InvalidArgument) => BINARY_FLAG_NAN_INVALID,
181        Some(Error::PrecisionRetryExhausted) => BINARY_FLAG_NAN_RETRY,
182        Some(Error::MemoryAllocation) => BINARY_FLAG_NAN_OOM,
183        Some(Error::ExponentOverflow(Sign::Pos)) => BINARY_FLAG_NAN_OVF_POS,
184        Some(Error::ExponentOverflow(Sign::Neg)) => BINARY_FLAG_NAN_OVF_NEG,
185        None => BINARY_FLAG_NAN_BARE,
186    }
187}
188
189fn nan_from_flag(flag: u8) -> Result<ExactNum, Error> {
190    match flag {
191        BINARY_FLAG_NAN_DIV0 => Ok(ExactNum::nan(Some(Error::DivisionByZero))),
192        BINARY_FLAG_NAN_INVALID => Ok(ExactNum::nan(Some(Error::InvalidArgument))),
193        BINARY_FLAG_NAN_RETRY => Ok(ExactNum::nan(Some(Error::PrecisionRetryExhausted))),
194        BINARY_FLAG_NAN_OOM => Ok(ExactNum::nan(Some(Error::MemoryAllocation))),
195        BINARY_FLAG_NAN_OVF_POS => Ok(ExactNum::nan(Some(Error::ExponentOverflow(Sign::Pos)))),
196        BINARY_FLAG_NAN_OVF_NEG => Ok(ExactNum::nan(Some(Error::ExponentOverflow(Sign::Neg)))),
197        BINARY_FLAG_NAN_BARE => Ok(NAN.clone()),
198        _ => Err(Error::InvalidArgument),
199    }
200}
201
202fn is_special_flag(flag: u8) -> bool {
203    matches!(
204        flag,
205        BINARY_FLAG_INF_POS
206            | BINARY_FLAG_INF_NEG
207            | BINARY_FLAG_NAN_DIV0
208            | BINARY_FLAG_NAN_INVALID
209            | BINARY_FLAG_NAN_RETRY
210            | BINARY_FLAG_NAN_OOM
211            | BINARY_FLAG_NAN_OVF_POS
212            | BINARY_FLAG_NAN_OVF_NEG
213            | BINARY_FLAG_NAN_BARE
214    )
215}
216
217fn write_special(flag: u8, dest: &mut [u8]) -> Result<usize, Error> {
218    if dest.len() < BINARY_INLINE_LEN {
219        return Err(Error::InvalidArgument);
220    }
221    dest[..BINARY_INLINE_LEN].fill(0);
222    dest[0] = flag;
223    dest[1] = BINARY_FORMAT_VERSION;
224    Ok(BINARY_INLINE_LEN)
225}
226
227fn write_inline(x: &ExactNum, dest: &mut [u8]) -> Result<usize, Error> {
228    if dest.len() < BINARY_INLINE_LEN {
229        return Err(Error::InvalidArgument);
230    }
231    if x.is_nan() {
232        return write_special(flag_nan(x.err()), dest);
233    }
234    if x.is_inf_pos() {
235        return write_special(BINARY_FLAG_INF_POS, dest);
236    }
237    if x.is_inf_neg() {
238        return write_special(BINARY_FLAG_INF_NEG, dest);
239    }
240    let Some((m, n_sig, sign, exp, inexact)) = x.as_raw_parts() else {
241        return Err(Error::InvalidArgument);
242    };
243    let n_u32 = u32_count(m.len());
244    if n_u32 > BINARY_INLINE_U32_WORDS {
245        return Err(Error::MemoryAllocation);
246    }
247    dest[..BINARY_INLINE_LEN].fill(0);
248    dest[0] = if sign == Sign::Neg { BINARY_FLAG_NEG } else { BINARY_FLAG_POS };
249    dest[1] = BINARY_FORMAT_VERSION;
250    dest[2] = u8::try_from(n_sig).map_err(|_| Error::InvalidArgument)?;
251    dest[3] = u8::from(inexact);
252    dest[4..8].copy_from_slice(&exp.to_be_bytes());
253    let mut tmp = [0u32; BINARY_INLINE_U32_WORDS];
254    words_to_u32(m, &mut tmp)?;
255    put_u32_be(&mut dest[8..12], tmp[0]);
256    put_u32_be(&mut dest[12..16], tmp[1]);
257    Ok(BINARY_INLINE_LEN)
258}
259
260fn encoded_len(x: &ExactNum) -> Result<usize, Error> {
261    if x.is_nan() || x.is_inf() {
262        return Ok(BINARY_INLINE_LEN);
263    }
264    let Some((m, _, _, _, _)) = x.as_raw_parts() else {
265        return Err(Error::InvalidArgument);
266    };
267    let n_u32 = u32_count(m.len());
268    if n_u32 <= BINARY_INLINE_U32_WORDS {
269        Ok(BINARY_INLINE_LEN)
270    } else {
271        heap_len(n_u32)
272    }
273}
274
275fn heap_len(n_u32: usize) -> Result<usize, Error> {
276    if n_u32 > BINARY_MAX_U32 {
277        return Err(Error::InvalidArgument);
278    }
279    n_u32
280        .checked_mul(U32_BYTES)
281        .and_then(|b| b.checked_add(BINARY_HEADER_LEN))
282        .ok_or(Error::InvalidArgument)
283}
284
285fn write_num(x: &ExactNum, dest: &mut [u8]) -> Result<usize, Error> {
286    if x.is_nan() || x.is_inf() {
287        return write_inline(x, dest);
288    }
289    let Some((m, n_sig, sign, exp, inexact)) = x.as_raw_parts() else {
290        return Err(Error::InvalidArgument);
291    };
292    let n_u32 = u32_count(m.len());
293    if n_u32 <= BINARY_INLINE_U32_WORDS {
294        return write_inline(x, dest);
295    }
296    let need = heap_len(n_u32)?;
297    if dest.len() < need {
298        return Err(Error::InvalidArgument);
299    }
300    dest[..need].fill(0);
301    dest[0] = if sign == Sign::Neg { BINARY_FLAG_HEAP_NEG } else { BINARY_FLAG_HEAP_POS };
302    dest[1] = BINARY_FORMAT_VERSION;
303    dest[3] = u8::from(inexact);
304    dest[4..8].copy_from_slice(&exp.to_be_bytes());
305    put_u32_be(&mut dest[8..12], u32_from_usize(n_sig)?);
306    put_u32_be(&mut dest[12..16], u32_from_usize(n_u32)?);
307    let mut limb = [0u32; 2];
308    let mut off = BINARY_HEADER_LEN;
309    for &w in m {
310        words_to_u32(core::slice::from_ref(&w), &mut limb[..WORD_U32])?;
311        for &u in limb.iter().take(WORD_U32) {
312            put_u32_be(&mut dest[off..off + U32_BYTES], u);
313            off += U32_BYTES;
314        }
315    }
316    Ok(need)
317}
318
319fn decode_num(bytes: &[u8]) -> Result<(ExactNum, usize), Error> {
320    if bytes.len() < 2 {
321        return Err(Error::InvalidArgument);
322    }
323    let flag = bytes[0];
324    if bytes[1] != BINARY_FORMAT_VERSION {
325        return Err(Error::InvalidArgument);
326    }
327    if flag == BINARY_FLAG_ARRAY {
328        return Err(Error::InvalidArgument);
329    }
330    if is_special_flag(flag) {
331        if bytes.len() < BINARY_INLINE_LEN {
332            return Err(Error::InvalidArgument);
333        }
334        let v = match flag {
335            BINARY_FLAG_INF_POS => INF_POS.clone(),
336            BINARY_FLAG_INF_NEG => INF_NEG.clone(),
337            _ => nan_from_flag(flag)?,
338        };
339        return Ok((v, BINARY_INLINE_LEN));
340    }
341    match flag {
342        BINARY_FLAG_POS | BINARY_FLAG_NEG => decode_inline_finite(bytes, flag),
343        BINARY_FLAG_HEAP_POS | BINARY_FLAG_HEAP_NEG => decode_heap(bytes, flag),
344        _ => Err(Error::InvalidArgument),
345    }
346}
347
348fn decode_inline_finite(bytes: &[u8], flag: u8) -> Result<(ExactNum, usize), Error> {
349    if bytes.len() < BINARY_INLINE_LEN {
350        return Err(Error::InvalidArgument);
351    }
352    let n_sig = bytes[2] as usize;
353    let inexact = bytes[3] != 0;
354    let exp = i32::from_be_bytes(bytes[4..8].try_into().map_err(|_| Error::InvalidArgument)?);
355    let u0 = u32::from_be_bytes(
356        bytes[8..12]
357            .try_into()
358            .map_err(|_| Error::InvalidArgument)?,
359    );
360    let u1 = u32::from_be_bytes(
361        bytes[12..16]
362            .try_into()
363            .map_err(|_| Error::InvalidArgument)?,
364    );
365    let sign = if flag == BINARY_FLAG_NEG { Sign::Neg } else { Sign::Pos };
366    let x = finite_from_u32(&[u0, u1], n_sig, sign, exp, inexact)?;
367    Ok((x, BINARY_INLINE_LEN))
368}
369
370fn decode_heap(bytes: &[u8], flag: u8) -> Result<(ExactNum, usize), Error> {
371    if bytes.len() < BINARY_HEADER_LEN {
372        return Err(Error::InvalidArgument);
373    }
374    let inexact = bytes[3] != 0;
375    let exp = i32::from_be_bytes(bytes[4..8].try_into().map_err(|_| Error::InvalidArgument)?);
376    let n_sig = u32::from_be_bytes(
377        bytes[8..12]
378            .try_into()
379            .map_err(|_| Error::InvalidArgument)?,
380    ) as usize;
381    let n_u32 = u32::from_be_bytes(
382        bytes[12..16]
383            .try_into()
384            .map_err(|_| Error::InvalidArgument)?,
385    ) as usize;
386    if n_u32 == 0 || n_u32 > BINARY_MAX_U32 {
387        return Err(Error::InvalidArgument);
388    }
389    let need = heap_len(n_u32)?;
390    if bytes.len() < need {
391        return Err(Error::InvalidArgument);
392    }
393    let mut u32s = Vec::new();
394    u32s.try_reserve_exact(n_u32)?;
395    let mut off = BINARY_HEADER_LEN;
396    for _ in 0..n_u32 {
397        let chunk: [u8; 4] = bytes[off..off + U32_BYTES]
398            .try_into()
399            .map_err(|_| Error::InvalidArgument)?;
400        u32s.push(u32::from_be_bytes(chunk));
401        off += U32_BYTES;
402    }
403    let sign = if flag == BINARY_FLAG_HEAP_NEG { Sign::Neg } else { Sign::Pos };
404    let x = finite_from_u32(&u32s, n_sig, sign, exp, inexact)?;
405    Ok((x, need))
406}
407
408fn finite_from_u32(
409    u32s: &[u32],
410    n_sig: usize,
411    sign: Sign,
412    exp: Exponent,
413    inexact: bool,
414) -> Result<ExactNum, Error> {
415    if u32s.len() % WORD_U32 != 0 {
416        return Err(Error::InvalidArgument);
417    }
418    if n_sig > u32s.len() * 32 {
419        return Err(Error::InvalidArgument);
420    }
421    let n_words = u32s.len() / WORD_U32;
422    let mut words = Vec::new();
423    words.try_reserve_exact(n_words)?;
424    for i in 0..n_words {
425        words.push(word_from_u32s(&u32s[i * WORD_U32..(i + 1) * WORD_U32]));
426    }
427    let x = ExactNum::from_raw_parts(&words, n_sig, sign, exp, inexact);
428    if x.is_nan() {
429        return Err(x.err().unwrap_or(Error::InvalidArgument));
430    }
431    Ok(x)
432}
433
434fn encoded_array_len(a: &ExactNumArray) -> Result<usize, Error> {
435    if a.len() > BINARY_MAX_ELEMS {
436        return Err(Error::InvalidArgument);
437    }
438    let mut n = BINARY_HEADER_LEN;
439    for v in a.as_slice() {
440        n = n
441            .checked_add(encoded_len(v)?)
442            .ok_or(Error::InvalidArgument)?;
443    }
444    Ok(n)
445}
446
447fn write_array(a: &ExactNumArray, dest: &mut [u8]) -> Result<usize, Error> {
448    let need = encoded_array_len(a)?;
449    if dest.len() < need {
450        return Err(Error::InvalidArgument);
451    }
452    let (rows, cols) = a.shape();
453    dest[..BINARY_HEADER_LEN].fill(0);
454    dest[0] = BINARY_FLAG_ARRAY;
455    dest[1] = BINARY_FORMAT_VERSION;
456    put_u32_be(&mut dest[4..8], u32_from_usize(rows)?);
457    put_u32_be(&mut dest[8..12], u32_from_usize(cols)?);
458    put_u32_be(&mut dest[12..16], u32_from_usize(a.precision())?);
459    let mut off = BINARY_HEADER_LEN;
460    for v in a.as_slice() {
461        off += write_num(v, &mut dest[off..])?;
462    }
463    Ok(need)
464}
465
466fn decode_array(bytes: &[u8]) -> Result<ExactNumArray, Error> {
467    if bytes.len() < BINARY_HEADER_LEN {
468        return Err(Error::InvalidArgument);
469    }
470    if bytes[0] != BINARY_FLAG_ARRAY || bytes[1] != BINARY_FORMAT_VERSION {
471        return Err(Error::InvalidArgument);
472    }
473    let rows =
474        u32::from_be_bytes(bytes[4..8].try_into().map_err(|_| Error::InvalidArgument)?) as usize;
475    let cols = u32::from_be_bytes(
476        bytes[8..12]
477            .try_into()
478            .map_err(|_| Error::InvalidArgument)?,
479    ) as usize;
480    let p = u32::from_be_bytes(
481        bytes[12..16]
482            .try_into()
483            .map_err(|_| Error::InvalidArgument)?,
484    ) as usize;
485    let n = rows.checked_mul(cols).ok_or(Error::InvalidArgument)?;
486    if n > BINARY_MAX_ELEMS {
487        return Err(Error::InvalidArgument);
488    }
489    let mut vals = Vec::new();
490    vals.try_reserve_exact(n)?;
491    let mut off = BINARY_HEADER_LEN;
492    for _ in 0..n {
493        let (v, used) = decode_num(&bytes[off..])?;
494        off += used;
495        vals.push(v);
496    }
497    if off != bytes.len() {
498        return Err(Error::InvalidArgument);
499    }
500    ExactNumArray::from_parts(p, rows, cols, vals)
501}
502
503fn u32_count(n_words: usize) -> usize {
504    n_words * WORD_U32
505}
506
507fn words_to_u32(words: &[Word], out: &mut [u32]) -> Result<(), Error> {
508    let need = u32_count(words.len());
509    if out.len() < need {
510        return Err(Error::InvalidArgument);
511    }
512    let mut i = 0;
513    for &w in words {
514        #[cfg(target_pointer_width = "32")]
515        {
516            out[i] = w;
517            i += 1;
518        }
519        #[cfg(not(target_pointer_width = "32"))]
520        {
521            out[i] = w as u32;
522            out[i + 1] = (w >> 32) as u32;
523            i += 2;
524        }
525    }
526    let _ = i;
527    Ok(())
528}
529
530fn word_from_u32s(part: &[u32]) -> Word {
531    #[cfg(target_pointer_width = "32")]
532    {
533        part[0]
534    }
535    #[cfg(not(target_pointer_width = "32"))]
536    {
537        (part[0] as Word) | ((part[1] as Word) << 32)
538    }
539}
540
541fn put_u32_be(dest: &mut [u8], v: u32) {
542    dest[..U32_BYTES].copy_from_slice(&v.to_be_bytes());
543}
544
545fn u32_from_usize(v: usize) -> Result<u32, Error> {
546    u32::try_from(v).map_err(|_| Error::InvalidArgument)
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552    use crate::RoundingMode;
553
554    fn p64() -> usize {
555        64
556    }
557
558    fn raw_eq(a: &ExactNum, b: &ExactNum) -> bool {
559        a.as_raw_parts() == b.as_raw_parts()
560    }
561
562    #[test]
563    fn binary_inline_roundtrip_finite() {
564        let p = p64();
565        let vals = [
566            ExactNum::from_u8(0, p),
567            ExactNum::from_u8(1, p),
568            ExactNum::from_u8(1, p).neg(),
569            ExactNum::from_u32(u32::MAX, p),
570            ExactNum::from_u8(1, p).div(&ExactNum::from_u8(3, p), p, RoundingMode::ToEven),
571        ];
572        for x in &vals {
573            let buf = x.to_inline_bytes().unwrap();
574            assert_eq!(buf.as_bytes().len(), BINARY_INLINE_LEN);
575            assert_eq!(buf.as_bytes()[1], BINARY_FORMAT_VERSION);
576            let y = ExactNum::from_inline_bytes(buf.as_bytes()).unwrap();
577            assert!(raw_eq(x, &y), "inline limbs");
578            assert_eq!(x.cmp(&y), Some(0));
579            let v = x.to_bytes().unwrap();
580            assert_eq!(v.len(), BINARY_INLINE_LEN);
581            let z = ExactNum::from_bytes(&v).unwrap();
582            assert!(raw_eq(x, &z));
583        }
584    }
585
586    #[test]
587    fn binary_nan_flag_is_recognizable() {
588        let buf = NAN.to_inline_bytes().unwrap();
589        assert_eq!(buf.as_bytes()[0], BINARY_FLAG_NAN_BARE);
590        assert_eq!(buf.as_bytes()[1], BINARY_FORMAT_VERSION);
591        let y = ExactNum::from_inline_bytes(buf.as_bytes()).unwrap();
592        assert!(y.is_nan());
593        let div = ExactNum::nan(Some(Error::DivisionByZero));
594        let d = div.to_bytes().unwrap();
595        assert_eq!(d[0], BINARY_FLAG_NAN_DIV0);
596        let back = ExactNum::from_bytes(&d).unwrap();
597        assert!(back.is_nan());
598        assert_eq!(back.err(), Some(Error::DivisionByZero));
599    }
600
601    #[test]
602    fn binary_inf_roundtrip() {
603        let p = INF_POS.to_bytes().unwrap();
604        let n = INF_NEG.to_bytes().unwrap();
605        assert_eq!(p[0], BINARY_FLAG_INF_POS);
606        assert_eq!(n[0], BINARY_FLAG_INF_NEG);
607        assert!(ExactNum::from_bytes(&p).unwrap().is_inf_pos());
608        assert!(ExactNum::from_bytes(&n).unwrap().is_inf_neg());
609    }
610
611    #[test]
612    fn binary_heap_roundtrip_p256() {
613        let p = 256;
614        let x = ExactNum::from_u8(1, p).div(&ExactNum::from_u8(3, p), p, RoundingMode::ToEven);
615        assert!(x.mantissa_max_bit_len().unwrap() > BINARY_INLINE_MANT_BITS);
616        assert!(x.to_inline_bytes().is_err());
617        let v = x.to_bytes().unwrap();
618        assert!(v.len() > BINARY_INLINE_LEN);
619        assert_eq!(v[0], BINARY_FLAG_HEAP_POS);
620        let y = ExactNum::from_bytes(&v).unwrap();
621        assert!(raw_eq(&x, &y));
622        assert_eq!(x.cmp(&y), Some(0));
623    }
624
625    #[test]
626    fn binary_array_roundtrip_and_shape_err() {
627        let p = p64();
628        let n = |k: u8| ExactNum::from_u8(k, p);
629        let a = ExactNumArray::from_shape(p, 2, 2, &[n(1), n(2), n(3), n(4)]).unwrap();
630        let v = a.to_bytes().unwrap();
631        assert_eq!(v[0], BINARY_FLAG_ARRAY);
632        let b = ExactNumArray::from_bytes(&v).unwrap();
633        assert_eq!(b.shape(), (2, 2));
634        assert_eq!(b.precision(), p);
635        for i in 0..4 {
636            assert_eq!(a.get(i).unwrap().cmp(b.get(i).unwrap()), Some(0));
637            assert!(raw_eq(a.get(i).unwrap(), b.get(i).unwrap()));
638        }
639        let mut bad = v.clone();
640        // rows = 3, cols = 2, but still four payloads → leftover or short read
641        bad[4..8].copy_from_slice(&3u32.to_be_bytes());
642        assert!(ExactNumArray::from_bytes(&bad).is_err());
643    }
644
645    #[test]
646    fn binary_invalid_bytes_are_err() {
647        assert!(ExactNum::from_bytes(&[]).is_err());
648        assert!(ExactNum::from_bytes(&[0]).is_err());
649        assert!(ExactNum::from_bytes(&[BINARY_FLAG_POS, 0]).is_err());
650        assert!(ExactNum::from_bytes(&[0x7F, BINARY_FORMAT_VERSION]).is_err());
651        let mut short = NAN.to_bytes().unwrap();
652        short.pop();
653        assert!(ExactNum::from_bytes(&short).is_err());
654        let mut extra = ExactNum::from_u8(1, p64()).to_bytes().unwrap();
655        extra.push(0);
656        assert!(ExactNum::from_bytes(&extra).is_err());
657        assert!(ExactNumArray::from_bytes(&[BINARY_FLAG_ARRAY, BINARY_FORMAT_VERSION]).is_err());
658    }
659
660    #[test]
661    fn binary_write_bytes_zero_alloc_inline() {
662        let x = ExactNum::from_u8(2, p64());
663        let mut dest = [0u8; BINARY_INLINE_LEN];
664        let n = x.write_inline_bytes(&mut dest).unwrap();
665        assert_eq!(n, BINARY_INLINE_LEN);
666        assert_eq!(dest, x.to_inline_bytes().unwrap().into_bytes());
667        assert!(x.write_inline_bytes(&mut dest[..8]).is_err());
668    }
669
670    #[test]
671    fn u32_word_boundary_add_is_2_pow_32() {
672        let p = p64();
673        let a = ExactNum::from_u32(u32::MAX, p);
674        let b = ExactNum::from_u8(1, p);
675        let s = a.add(&b, p, RoundingMode::ToEven);
676        let expect = ExactNum::from_u64(1u64 << 32, p);
677        assert_eq!(s.cmp(&expect), Some(0));
678        assert!(s.err().is_none());
679        let buf = s.to_inline_bytes().unwrap();
680        assert!(raw_eq(
681            &s,
682            &ExactNum::from_inline_bytes(buf.as_bytes()).unwrap()
683        ));
684    }
685}