Add float to bytes conversion and vice-versa (#4989)

Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Tim Voßhenrich 2024-09-26 11:14:45 +02:00 committed by GitHub
parent 4827f28a94
commit 5823429a96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 4 deletions

View File

@ -2,7 +2,10 @@ use std::num::ParseFloatError;
use ecow::{eco_format, EcoString};
use crate::foundations::{cast, func, repr, scope, ty, Repr, Str};
use crate::diag::StrResult;
use crate::foundations::{
bail, cast, func, repr, scope, ty, Bytes, Endianness, Repr, Str,
};
use crate::layout::Ratio;
/// A floating-point number.
@ -106,6 +109,58 @@ impl f64 {
pub fn signum(self) -> f64 {
f64::signum(self)
}
/// Converts bytes to a float.
///
/// ```example
/// #float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))) \
/// #float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big")
/// ```
#[func]
pub fn from_bytes(
/// The bytes that should be converted to a float.
///
/// Must be of length exactly 8 so that the result fits into a 64-bit
/// float.
bytes: Bytes,
/// The endianness of the conversion.
#[named]
#[default(Endianness::Little)]
endian: Endianness,
) -> StrResult<f64> {
// Convert slice to an array of length 8.
let buf: [u8; 8] = match bytes.as_ref().try_into() {
Ok(buffer) => buffer,
Err(_) => bail!("bytes must have a length of exactly 8"),
};
Ok(match endian {
Endianness::Little => f64::from_le_bytes(buf),
Endianness::Big => f64::from_be_bytes(buf),
})
}
/// Converts a float to bytes.
///
/// ```example
/// #array(1.0.to-bytes(endian: "big")) \
/// #array(1.0.to-bytes())
/// ```
#[func]
pub fn to_bytes(
self,
/// The endianness of the conversion.
#[named]
#[default(Endianness::Little)]
endian: Endianness,
) -> Bytes {
match endian {
Endianness::Little => self.to_le_bytes(),
Endianness::Big => self.to_be_bytes(),
}
.as_slice()
.into()
}
}
impl Repr for f64 {

View File

@ -341,12 +341,15 @@ impl Repr for i64 {
}
}
/// Represents the byte order used for converting integers to bytes and vice versa.
/// Represents the byte order used for converting integers and floats to bytes
/// and vice versa.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Endianness {
/// Big-endian byte order: the highest-value byte is at the beginning of the bytes.
/// Big-endian byte order: The highest-value byte is at the beginning of the
/// bytes.
Big,
/// Little-endian byte order: the lowest-value byte is at the beginning of the bytes.
/// Little-endian byte order: The lowest-value byte is at the beginning of
/// the bytes.
Little,
}

View File

@ -42,6 +42,17 @@
#test(float(-calc.inf).signum(), -1.0)
#test(float(float.nan).signum().is-nan(), true)
--- float-from-and-to-bytes ---
// Test float `from-bytes()` and `to-bytes()`.
#test(float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))), 1.0)
#test(float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big"), 1.0)
#test(1.0.to-bytes(), bytes((0, 0, 0, 0, 0, 0, 240, 63)))
#test(1.0.to-bytes(endian: "big"), bytes((63, 240, 0, 0, 0, 0, 0, 0)))
--- float-from-bytes-bad-length ---
// Error: 2-54 bytes must have a length of exactly 8
#float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 0, 1, 0)))
--- float-repr ---
// Test the `repr` function with floats.
#repr(12.0) \