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}