Bugfix/loss of precision when parsing value with unit (#15606)

Closes #12858

# Description
As explained in the ticket, easy to reproduce. Example: 1.07 minute is
1.07*60=64.2 secondes
```nushell
# before - wrong
> 1.07min
1min 4sec

# now - right
> 1.07min
1min 4sec 200ms
```

# User-Facing Changes
Bug is fixed when using ``into duration``.

# Tests + Formatting
Added a test for ``into duration``
Fixed ``parse_long_duration`` test: we gained precision 😄 

# After Submitting
Release notes? Or blog is enough? Let me know
This commit is contained in:
Loïc Riegel
2025-04-20 00:02:40 +02:00
committed by GitHub
parent 24dba9dc53
commit 1503ee09ba
5 changed files with 52 additions and 12 deletions

View File

@ -2701,12 +2701,31 @@ pub fn parse_unit_value<'res>(
}
});
let (num, unit) = match convert {
Some(convert_to) => (
((number_part * convert_to.1 as f64) + (decimal_part * convert_to.1 as f64)) as i64,
convert_to.0,
),
None => (number_part as i64, *unit),
let mut unit = match convert {
Some(convert_to) => convert_to.0,
None => *unit,
};
let num_float = match convert {
Some(convert_to) => {
(number_part * convert_to.1 as f64) + (decimal_part * convert_to.1 as f64)
}
None => number_part,
};
// Convert all durations to nanoseconds to not lose precision
let num = match unit_to_ns_factor(&unit) {
Some(factor) => {
let num_ns = num_float * factor;
if i64::MIN as f64 <= num_ns && num_ns <= i64::MAX as f64 {
unit = Unit::Nanosecond;
num_ns as i64
} else {
// not safe to convert, because of the overflow
num_float as i64
}
}
None => num_float as i64,
};
trace!("-- found {} {:?}", num, unit);
@ -2813,6 +2832,20 @@ pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
(Unit::Week, "wk", Some((Unit::Day, 7))),
];
fn unit_to_ns_factor(unit: &Unit) -> Option<f64> {
match unit {
Unit::Nanosecond => Some(1.0),
Unit::Microsecond => Some(1_000.0),
Unit::Millisecond => Some(1_000_000.0),
Unit::Second => Some(1_000_000_000.0),
Unit::Minute => Some(60.0 * 1_000_000_000.0),
Unit::Hour => Some(60.0 * 60.0 * 1_000_000_000.0),
Unit::Day => Some(24.0 * 60.0 * 60.0 * 1_000_000_000.0),
Unit::Week => Some(7.0 * 24.0 * 60.0 * 60.0 * 1_000_000_000.0),
_ => None,
}
}
// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
fn modf(x: f64) -> (f64, f64) {
let rv2: f64;
@ -6878,13 +6911,9 @@ pub fn parse(
let mut output = {
if let Some(block) = previously_parsed_block {
// dbg!("previous block");
return block;
} else {
// dbg!("starting lex");
let (output, err) = lex(contents, new_span.start, &[], &[], false);
// dbg!("finished lex");
// dbg!(&output);
if let Some(err) = err {
working_set.error(err)
}