-
Notifications
You must be signed in to change notification settings - Fork 8
add parsing variant that doesn't allocate #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,8 @@ | |
| use std::cmp::Ordering; | ||
| use std::convert::{TryFrom, TryInto}; | ||
| use std::ffi::{CStr, CString}; | ||
| use std::fmt; | ||
| use std::fmt::{self, LowerExp}; | ||
| use std::io::Write; | ||
| use std::iter::{Product, Sum}; | ||
| use std::marker::PhantomData; | ||
| use std::mem::MaybeUninit; | ||
|
|
@@ -1400,13 +1401,18 @@ impl<const N: usize> Context<Decimal<N>> { | |
| where | ||
| S: Into<Vec<u8>>, | ||
| { | ||
| validate_n(N); | ||
| let c_string = CString::new(s).map_err(|_| ParseDecimalError)?; | ||
| self.parse_c_str(c_string.as_c_str()) | ||
| } | ||
|
|
||
| /// Parses a number from its string representation. | ||
| pub fn parse_c_str(&mut self, s: &CStr) -> Result<Decimal<N>, ParseDecimalError> { | ||
| validate_n(N); | ||
| let mut d = Decimal::zero(); | ||
| unsafe { | ||
| decnumber_sys::decNumberFromString( | ||
| d.as_mut_ptr() as *mut decnumber_sys::decNumber, | ||
| c_string.as_ptr(), | ||
| s.as_ptr(), | ||
| &mut self.inner, | ||
| ); | ||
| }; | ||
|
|
@@ -1744,7 +1750,7 @@ impl<const N: usize> Context<Decimal<N>> { | |
| /// Both of these are guaranteed to fit comfortably within `Decimal`'s | ||
| /// constraints. | ||
| pub fn from_f32(&mut self, n: f32) -> Decimal<N> { | ||
| self.parse(n.to_string().as_str()).unwrap() | ||
| self.from_float(n) | ||
| } | ||
|
|
||
| /// Converts an `f64` to a `Decimal<N>`. | ||
|
|
@@ -1756,7 +1762,45 @@ impl<const N: usize> Context<Decimal<N>> { | |
| /// Both of these are guaranteed to fit comfortably within `Decimal`'s | ||
| /// constraints. | ||
| pub fn from_f64(&mut self, n: f64) -> Decimal<N> { | ||
| self.parse(n.to_string().as_str()).unwrap() | ||
| self.from_float(n) | ||
| } | ||
|
|
||
| /// Converts a `f32` or a `f64` value to a `Decimal<N>`. | ||
| /// | ||
| /// Note that this conversion is infallible because `f64`'s: | ||
| /// - Maximum precision is ~18 | ||
| /// - Min/max exponent is ~ -305, 305 | ||
| /// | ||
| /// Both of these are guaranteed to fit comfortably within `Decimal`'s | ||
| /// constraints. | ||
| // NOTE: The code is generic over any T: LowerExp but passing something like f128 wouldn't | ||
| // always work since there are f128 values that don't fit in 24 bytes. | ||
| pub fn from_float<T: LowerExp>(&mut self, n: T) -> Decimal<N> { | ||
| // The maximum bytes needed to store the decimal representation of a f64. | ||
| // This is because you have at most: | ||
| // * 1 byte for a possible leading negative sign | ||
| // * 17 bytes of significant digits | ||
| // See: https://stdrs.dev/nightly/x86_64-pc-windows-gnu/core/num/flt2dec/constant.MAX_SIG_DIGITS.html | ||
| // * 1 byte for a decimal point | ||
| // * 2 bytes for a negative exponent (e-) | ||
| // * 3 bytes for the largest possible exponent (308) | ||
| // An example of such maximal float value is f64::from_bits(0x8008000000000000) whose | ||
| // decimal representation is '-1.1125369292536007e-308' | ||
| const MAX_LEN: usize = 24; | ||
|
|
||
| // Create a buffer that can hold the longest float plus a nul character for the C string | ||
| let mut buf = [0u8; MAX_LEN + 1]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like this +1 is to ensure the last byte is a null character for the C String? mind documenting that? |
||
| let mut unwritten = &mut buf[..MAX_LEN]; | ||
| write!(unwritten, "{:e}", n).unwrap(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Talked on slack, doesn't seem to be much we do here so leaving it as-is |
||
| let unwritten_len = unwritten.len(); | ||
| // SAFETY: | ||
| // * buf was zero-initialized | ||
| // * Exactly MAX_LEN - unwritten.len() bytes have been modified | ||
| // * Formatting a float never writes nul characters | ||
| // Therefore the produced slice contains exactly one nul character | ||
| let c_buf = | ||
| unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..MAX_LEN - unwritten_len + 1]) }; | ||
| self.parse_c_str(c_buf).unwrap() | ||
| } | ||
|
|
||
| /// Computes the digitwise logical inversion of `n`, storing the result in | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,4 +10,4 @@ decnumber-sys = { path = "../decnumber-sys" } | |
| libc = "0.2.68" | ||
|
|
||
| [build-dependencies] | ||
| ctest = "0.2" | ||
| ctest = "0.4" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense to me! Just double checked and ryu also sizes their buffer with 24 bytes so these seems quite safe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to see it validated elsewhere!