Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions dec/benches/dec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

use std::convert::TryFrom;

use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use rand::{thread_rng, Rng};

use dec::{Context, Decimal, Decimal128, Decimal64};
Expand All @@ -42,6 +42,17 @@ pub fn bench_decode(c: &mut Criterion) {
c.bench_function("decode_decimal128", |b| bench_decode_decimal128(d128, b));
}

pub fn bench_parse(c: &mut Criterion) {
let mut cx = Context::<Decimal<13>>::default();
c.bench_function("parse_decimal", |b| {
b.iter(|| {
black_box(cx.from_f64(black_box(f64::MIN)));
black_box(cx.from_f64(black_box(f64::from_bits(0x8008000000000000))));
black_box(cx.from_f64(black_box(f64::MAX)));
})
});
}

pub fn bench_to_string(d: Decimal128, b: &mut Bencher) {
b.iter_with_setup(
|| {
Expand Down Expand Up @@ -177,5 +188,11 @@ pub fn bench_tryinto_primitive(c: &mut Criterion) {
});
}

criterion_group!(benches, bench_decode, bench_print, bench_tryinto_primitive);
criterion_group!(
benches,
bench_decode,
bench_print,
bench_tryinto_primitive,
bench_parse
);
criterion_main!(benches);
54 changes: 49 additions & 5 deletions dec/src/decimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
);
};
Expand Down Expand Up @@ -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>`.
Expand All @@ -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;

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

Copy link
Contributor Author

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!


// Create a buffer that can hold the longest float plus a nul character for the C string
let mut buf = [0u8; MAX_LEN + 1];

Choose a reason for hiding this comment

The 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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the write! macro has a decent amount of machinery internally that it might be nice to bypass, not a huge priority though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Down
2 changes: 1 addition & 1 deletion systest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ decnumber-sys = { path = "../decnumber-sys" }
libc = "0.2.68"

[build-dependencies]
ctest = "0.2"
ctest = "0.4"