ibmfloat/
lib.rs

1#![deny(
2    missing_docs,
3    missing_copy_implementations,
4    trivial_casts,
5    trivial_numeric_casts,
6    unsafe_code,
7    unstable_features,
8    unused_import_braces,
9    unused_qualifications
10)]
11#![cfg_attr(not(feature = "std"), no_std)]
12#![cfg_attr(feature = "std", deny(missing_debug_implementations))]
13
14//! A Rust library for [IBM floating point
15//! numbers](https://en.wikipedia.org/wiki/IBM_hexadecimal_floating_point), specifically focused on
16//! converting them to IEEE-754 floating point values.
17//!
18//! See [`F32`](struct.F32.html) for 32-bit floats and [`F64`](struct.F64.html) for 64-bit floats.
19
20#[cfg(feature = "std")]
21use std::{cmp, fmt};
22
23#[cfg(not(feature = "std"))]
24use core::cmp;
25
26mod convert;
27
28/// A 32-bit IBM floating point number.
29///
30/// This type supports the conversions:
31///
32/// * Transmuting to/from a `u32` via `from_bits()`, `to_bits()`
33/// * Transmuting to/from a big-endian `[u8; 4]` via `from_be_bytes()`/`to_be_bytes()`
34/// * Lossily converting to an `f32` via `From`/`Into`
35/// * Losslessly converting to an `f64` via `From`/`Into`
36///
37/// IBM `F32` floats have slightly less precision than IEEE-754 `f32` floats, but it covers a
38/// slightly larger domain. `F32`s of typical magnitude can be converted to `f32` without rounding
39/// or other loss of precision. Converting `F32`s of large magnitude to `f32` will cause rounding;
40/// `F32`s of extreme magnitude can also cause overflow and underflow to occur.
41///
42/// Every `F32` can be precisely represented as an `f64`, without rounding, overflow, or underflow.
43/// Those seeking a lossless path to IEEE-754 should convert `F32` to `f64`.
44///
45/// ```
46/// // Use the example -118.625:
47/// //   https://en.wikipedia.org/wiki/IBM_hexadecimal_floating_point#Example
48/// let foreign_float = ibmfloat::F32::from_bits(0b1_1000010_0111_0110_1010_0000_0000_0000);
49///
50/// let native_float = f32::from(foreign_float);
51/// assert_eq!(native_float, -118.625f32);
52///
53/// let native_float: f32 = foreign_float.into();
54/// assert_eq!(native_float, -118.625f32);
55/// ```
56#[derive(Copy, Clone)]
57#[repr(transparent)]
58pub struct F32(u32);
59
60impl F32 {
61    /// Transmute a native-endian `u64` into an `F64`.
62    ///
63    /// ```
64    /// let foreign_float = ibmfloat::F32::from_bits(0x46000001);
65    ///
66    /// let native_float = f32::from(foreign_float); // potential loss of precision
67    /// assert_eq!(native_float, 1.0f32);
68    ///
69    /// let native_float = f64::from(foreign_float); // always exact
70    /// assert_eq!(native_float, 1.0f64);
71    /// ```
72    #[inline]
73    pub fn from_bits(value: u32) -> Self {
74        Self(value)
75    }
76
77    /// Transmute this `F32` to a native-endian `u32`.
78    ///
79    /// ```
80    /// let foreign_float = ibmfloat::F32::from_bits(0x46000001);
81    ///
82    /// assert_eq!(foreign_float.to_bits(), 0x46000001);
83    /// ```
84    #[inline]
85    pub fn to_bits(self) -> u32 {
86        self.0
87    }
88
89    /// Create a floating point value from its representation as a byte array in big endian.
90    ///
91    /// ```
92    /// let foreign_float = ibmfloat::F32::from_be_bytes([0x46, 0, 0, 1]);
93    ///
94    /// assert_eq!(foreign_float.to_bits(), 0x46000001);
95    ///
96    /// let native_float = f32::from(foreign_float);
97    /// assert_eq!(native_float, 1.0f32);
98    /// ```
99    #[inline]
100    pub fn from_be_bytes(bytes: [u8; 4]) -> Self {
101        Self(u32::from_be_bytes(bytes))
102    }
103
104    /// Return the memory representation of this floating point number as a byte array in big-endian
105    /// (network) byte order.
106    ///
107    /// ```
108    /// let foreign_float = ibmfloat::F32::from_bits(0x46000001);
109    ///
110    /// assert_eq!(foreign_float.to_be_bytes(), [0x46, 0, 0, 1]);
111    /// ```
112    #[inline]
113    pub fn to_be_bytes(self) -> [u8; 4] {
114        self.0.to_be_bytes()
115    }
116}
117
118/// A 64-bit IBM floating point number.
119///
120/// This type supports the conversions:
121///
122/// * Transmuting to/from a `u64` via `from_bits()`, `to_bits()`
123/// * Transmuting to/from a big-endian `[u8; 8]` via `from_be_bytes()`/`to_be_bytes()`
124/// * Lossily converting to an `f32` via `From`/`Into`
125/// * Lossily converting to an `f64` via `From`/`Into`
126///
127/// IBM `F64` floats have slightly more precision than IEEE-754 `f64` floats, but they cover a
128/// slightly smaller domain. Most conversions will require rounding, but there is no risk of
129/// overflow or underflow.
130///
131/// ```
132/// let foreign_float = ibmfloat::F64::from_bits(0x4110000000000000);
133///
134/// let native_float = f64::from(foreign_float);
135/// assert_eq!(native_float, 1.0f64);
136///
137/// let native_float: f64 = foreign_float.into();
138/// assert_eq!(native_float, 1.0f64);
139/// ```
140#[derive(Copy, Clone)]
141#[repr(transparent)]
142pub struct F64(u64);
143
144impl F64 {
145    /// Transmute a native-endian `u64` into an `F64`.
146    ///
147    /// ```
148    /// let foreign_float = ibmfloat::F64::from_bits(0x4110000000000000);
149    ///
150    /// let native_float = f64::from(foreign_float);
151    /// assert_eq!(native_float, 1.0f64);
152    /// ```
153    #[inline]
154    pub fn from_bits(value: u64) -> Self {
155        Self(value)
156    }
157
158    /// Transmute this `F64` to a native-endian `u64`.
159    ///
160    /// ```
161    /// let foreign_float = ibmfloat::F64::from_bits(0x4110000000000000);
162    ///
163    /// assert_eq!(foreign_float.to_bits(), 0x4110000000000000);
164    /// ```
165    #[inline]
166    pub fn to_bits(self) -> u64 {
167        self.0
168    }
169
170    /// Create a floating point value from its representation as a byte array in big endian.
171    ///
172    /// ```
173    /// let foreign_float = ibmfloat::F64::from_be_bytes([0x41, 0x10, 0, 0, 0, 0, 0, 0]);
174    ///
175    /// assert_eq!(foreign_float.to_bits(), 0x4110000000000000);
176    ///
177    /// let native_float = f64::from(foreign_float);
178    /// assert_eq!(native_float, 1.0f64);
179    /// ```
180    #[inline]
181    pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
182        Self(u64::from_be_bytes(bytes))
183    }
184
185    /// Return the memory representation of this floating point number as a byte array in big-endian
186    /// (network) byte order.
187    ///
188    /// ```
189    /// let foreign_float = ibmfloat::F64::from_bits(0x4110000000000000);
190    ///
191    /// assert_eq!(foreign_float.to_be_bytes(), [0x41, 0x10, 0, 0, 0, 0, 0, 0]);
192    /// ```
193    #[inline]
194    pub fn to_be_bytes(self) -> [u8; 8] {
195        self.0.to_be_bytes()
196    }
197}
198
199macro_rules! float {
200    ($t:ty) => {
201        // Convert everything to an f64 and implement Debug, PartialEq, PartialOrd over top
202
203        #[cfg(feature = "std")]
204        impl fmt::Debug for $t {
205            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206                f64::from(*self).fmt(f)
207            }
208        }
209
210        impl PartialEq for $t {
211            fn eq(&self, other: &Self) -> bool {
212                f64::from(*self).eq(&f64::from(*other))
213            }
214        }
215
216        impl PartialOrd for $t {
217            fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
218                f64::from(*self).partial_cmp(&f64::from(*other))
219            }
220        }
221    };
222}
223float!(F32);
224float!(F64);
225
226impl From<F32> for f32 {
227    #[inline]
228    fn from(v: F32) -> Self {
229        f32::from_bits(convert::ibm32ieee32(v.0))
230    }
231}
232
233impl From<F32> for f64 {
234    #[inline]
235    fn from(v: F32) -> Self {
236        f64::from_bits(convert::ibm32ieee64(v.0))
237    }
238}
239
240impl From<F64> for f32 {
241    #[inline]
242    fn from(v: F64) -> Self {
243        f32::from_bits(convert::ibm64ieee32(v.0))
244    }
245}
246
247impl From<F64> for f64 {
248    #[inline]
249    fn from(v: F64) -> Self {
250        f64::from_bits(convert::ibm64ieee64(v.0))
251    }
252}