From 2f04c172fe74981be82149823d96574f7ff4383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:12:53 +0300 Subject: [PATCH 1/4] Add floating point support for ranges --- crates/nu-protocol/src/value/range.rs | 41 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index ff138e4af6..2832d09b2b 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use crate::{ ast::{RangeInclusion, RangeOperator}, *, @@ -150,31 +152,46 @@ impl RangeIterator { } } +// Compare two floating point numbers. The decision interval for equality is dynamically scaled +// based on the value being compared. +fn compare_floats(val: f64, other: f64) -> Option { + let prec = val * f64::EPSILON; + + if (other - val).abs() < prec.abs() { + return Some(Ordering::Equal); + } + + val.partial_cmp(&other) +} + impl Iterator for RangeIterator { type Item = Value; fn next(&mut self) -> Option { - use std::cmp::Ordering; if self.done { return None; } let ordering = if matches!(self.end, Value::Nothing { .. }) { - Ordering::Less + Some(Ordering::Less) } else { match (&self.curr, &self.end) { - (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - // (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => curr.cmp(end), - // (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => curr.cmp(end), - _ => { - self.done = true; - return Some(Value::Error { - error: ShellError::CannotCreateRange(self.span), - }); - } + (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)), + (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr, *end), + (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => compare_floats(*curr, *end as f64), + (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr as f64, *end), + _ => None, } }; + let ordering = if let Some(ord) = ordering { + ord + } else { + self.done = true; + return Some(Value::Error { + error: ShellError::CannotCreateRange(self.span), + }); + }; + let desired_ordering = if self.moves_up { Ordering::Less } else { From 013b12a86451556903ea1866010864d234afdbfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:55:11 +0300 Subject: [PATCH 2/4] Do not allow precision interval to rach < epsilon --- crates/nu-protocol/src/value/range.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 2832d09b2b..44a97d7b59 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -153,11 +153,11 @@ impl RangeIterator { } // Compare two floating point numbers. The decision interval for equality is dynamically scaled -// based on the value being compared. +// as the value being compared increases in magnitude. fn compare_floats(val: f64, other: f64) -> Option { - let prec = val * f64::EPSILON; + let prec = f64::EPSILON.max(val.abs() * f64::EPSILON); - if (other - val).abs() < prec.abs() { + if (other - val).abs() < prec { return Some(Ordering::Equal); } From 9936946eb57306ccfae5bdc70b61f8b20ee38f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 14:58:32 +0300 Subject: [PATCH 3/4] Fmt --- crates/nu-protocol/src/value/range.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 44a97d7b59..80eb568827 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -176,9 +176,15 @@ impl Iterator for RangeIterator { } else { match (&self.curr, &self.end) { (Value::Int { val: curr, .. }, Value::Int { val: end, .. }) => Some(curr.cmp(end)), - (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr, *end), - (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => compare_floats(*curr, *end as f64), - (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => compare_floats(*curr as f64, *end), + (Value::Float { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr, *end) + } + (Value::Float { val: curr, .. }, Value::Int { val: end, .. }) => { + compare_floats(*curr, *end as f64) + } + (Value::Int { val: curr, .. }, Value::Float { val: end, .. }) => { + compare_floats(*curr as f64, *end) + } _ => None, } }; From ce0b5bf4ab43cb60f360873d1a0fece0b06d2750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Sep 2021 15:36:54 +0300 Subject: [PATCH 4/4] Add test for float ranges --- crates/nu-parser/tests/test_parser.rs | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 2015f356c0..e0359378c2 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -403,6 +403,38 @@ mod range { } } + #[test] + fn parse_float_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse(&mut working_set, None, b"2.0..4.0..10.0", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + #[test] fn bad_parse_does_crash() { let engine_state = EngineState::new();