use std::borrow::Borrow;
use std::fmt;
use std::fmt::{Display, Formatter};
use arrow::compute::{cast_with_options, CastOptions};
use arrow::datatypes::DataType;
use datafusion_common::{DataFusionError, Result, ScalarValue};
use datafusion_expr::type_coercion::binary::get_result_type;
use datafusion_expr::Operator;
use crate::aggregate::min_max::{max, min};
use crate::intervals::rounding::alter_fp_rounding_mode;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntervalBound {
pub value: ScalarValue,
pub open: bool,
}
impl IntervalBound {
pub const fn new(value: ScalarValue, open: bool) -> IntervalBound {
IntervalBound { value, open }
}
pub fn make_unbounded<T: Borrow<DataType>>(data_type: T) -> Result<Self> {
ScalarValue::try_from(data_type.borrow()).map(|v| IntervalBound::new(v, true))
}
pub fn get_datatype(&self) -> DataType {
self.value.get_datatype()
}
pub fn is_unbounded(&self) -> bool {
self.value.is_null()
}
pub(crate) fn cast_to(
&self,
data_type: &DataType,
cast_options: &CastOptions,
) -> Result<IntervalBound> {
cast_scalar_value(&self.value, data_type, cast_options)
.map(|value| IntervalBound::new(value, self.open))
}
pub fn add<const UPPER: bool, T: Borrow<IntervalBound>>(
&self,
other: T,
) -> Result<IntervalBound> {
let rhs = other.borrow();
if self.is_unbounded() || rhs.is_unbounded() {
return IntervalBound::make_unbounded(get_result_type(
&self.get_datatype(),
&Operator::Plus,
&rhs.get_datatype(),
)?);
}
match self.get_datatype() {
DataType::Float64 | DataType::Float32 => {
alter_fp_rounding_mode::<UPPER, _>(&self.value, &rhs.value, |lhs, rhs| {
lhs.add(rhs)
})
}
_ => self.value.add(&rhs.value),
}
.map(|v| IntervalBound::new(v, self.open || rhs.open))
}
pub fn sub<const UPPER: bool, T: Borrow<IntervalBound>>(
&self,
other: T,
) -> Result<IntervalBound> {
let rhs = other.borrow();
if self.is_unbounded() || rhs.is_unbounded() {
return IntervalBound::make_unbounded(get_result_type(
&self.get_datatype(),
&Operator::Minus,
&rhs.get_datatype(),
)?);
}
match self.get_datatype() {
DataType::Float64 | DataType::Float32 => {
alter_fp_rounding_mode::<UPPER, _>(&self.value, &rhs.value, |lhs, rhs| {
lhs.sub(rhs)
})
}
_ => self.value.sub(&rhs.value),
}
.map(|v| IntervalBound::new(v, self.open || rhs.open))
}
pub fn choose(
first: &IntervalBound,
second: &IntervalBound,
decide: fn(&ScalarValue, &ScalarValue) -> Result<ScalarValue>,
) -> Result<IntervalBound> {
Ok(if first.is_unbounded() {
second.clone()
} else if second.is_unbounded() {
first.clone()
} else if first.value != second.value {
let chosen = decide(&first.value, &second.value)?;
if chosen.eq(&first.value) {
first.clone()
} else {
second.clone()
}
} else {
IntervalBound::new(second.value.clone(), first.open || second.open)
})
}
}
impl Display for IntervalBound {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "IntervalBound [{}]", self.value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interval {
pub lower: IntervalBound,
pub upper: IntervalBound,
}
impl Default for Interval {
fn default() -> Self {
Interval::new(
IntervalBound::new(ScalarValue::Null, true),
IntervalBound::new(ScalarValue::Null, true),
)
}
}
impl Display for Interval {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Interval [{}, {}]", self.lower, self.upper)
}
}
impl Interval {
pub fn new(lower: IntervalBound, upper: IntervalBound) -> Interval {
if let ScalarValue::Boolean(_) = lower.value {
let standardized_lower = match lower.value {
ScalarValue::Boolean(None) if lower.open => {
ScalarValue::Boolean(Some(false))
}
ScalarValue::Boolean(Some(false)) if lower.open => {
ScalarValue::Boolean(Some(true))
}
_ => lower.value,
};
let standardized_upper = match upper.value {
ScalarValue::Boolean(None) if upper.open => {
ScalarValue::Boolean(Some(true))
}
ScalarValue::Boolean(Some(true)) if upper.open => {
ScalarValue::Boolean(Some(false))
}
_ => upper.value,
};
Interval {
lower: IntervalBound::new(standardized_lower, false),
upper: IntervalBound::new(standardized_upper, false),
}
} else {
Interval { lower, upper }
}
}
pub fn make<T>(lower: Option<T>, upper: Option<T>, open: (bool, bool)) -> Interval
where
ScalarValue: From<Option<T>>,
{
Interval::new(
IntervalBound::new(ScalarValue::from(lower), open.0),
IntervalBound::new(ScalarValue::from(upper), open.1),
)
}
pub(crate) fn cast_to(
&self,
data_type: &DataType,
cast_options: &CastOptions,
) -> Result<Interval> {
let lower = self.lower.cast_to(data_type, cast_options)?;
let upper = self.upper.cast_to(data_type, cast_options)?;
Ok(Interval::new(lower, upper))
}
pub(crate) fn get_datatype(&self) -> Result<DataType> {
let lower_type = self.lower.get_datatype();
let upper_type = self.upper.get_datatype();
if lower_type == upper_type {
Ok(lower_type)
} else {
Err(DataFusionError::Internal(format!(
"Interval bounds have different types: {lower_type} != {upper_type}",
)))
}
}
pub(crate) fn gt<T: Borrow<Interval>>(&self, other: T) -> Interval {
let rhs = other.borrow();
let flags = if !self.upper.is_unbounded()
&& !rhs.lower.is_unbounded()
&& self.upper.value <= rhs.lower.value
{
(false, false)
} else if !self.lower.is_unbounded()
&& !rhs.upper.is_unbounded()
&& self.lower.value >= rhs.upper.value
&& (self.lower.value > rhs.upper.value || self.lower.open || rhs.upper.open)
{
(true, true)
} else {
(false, true)
};
Interval::make(Some(flags.0), Some(flags.1), (false, false))
}
pub(crate) fn gt_eq<T: Borrow<Interval>>(&self, other: T) -> Interval {
let rhs = other.borrow();
let flags = if !self.lower.is_unbounded()
&& !rhs.upper.is_unbounded()
&& self.lower.value >= rhs.upper.value
{
(true, true)
} else if !self.upper.is_unbounded()
&& !rhs.lower.is_unbounded()
&& self.upper.value <= rhs.lower.value
&& (self.upper.value < rhs.lower.value || self.upper.open || rhs.lower.open)
{
(false, false)
} else {
(false, true)
};
Interval::make(Some(flags.0), Some(flags.1), (false, false))
}
pub(crate) fn lt<T: Borrow<Interval>>(&self, other: T) -> Interval {
other.borrow().gt(self)
}
pub(crate) fn lt_eq<T: Borrow<Interval>>(&self, other: T) -> Interval {
other.borrow().gt_eq(self)
}
pub(crate) fn equal<T: Borrow<Interval>>(&self, other: T) -> Interval {
let rhs = other.borrow();
let flags = if !self.lower.is_unbounded()
&& (self.lower.value == self.upper.value)
&& (rhs.lower.value == rhs.upper.value)
&& (self.lower.value == rhs.lower.value)
{
(true, true)
} else if self.gt(rhs) == Interval::CERTAINLY_TRUE
|| self.lt(rhs) == Interval::CERTAINLY_TRUE
{
(false, false)
} else {
(false, true)
};
Interval::make(Some(flags.0), Some(flags.1), (false, false))
}
pub(crate) fn and<T: Borrow<Interval>>(&self, other: T) -> Result<Interval> {
let rhs = other.borrow();
match (
&self.lower.value,
&self.upper.value,
&rhs.lower.value,
&rhs.upper.value,
) {
(
ScalarValue::Boolean(Some(self_lower)),
ScalarValue::Boolean(Some(self_upper)),
ScalarValue::Boolean(Some(other_lower)),
ScalarValue::Boolean(Some(other_upper)),
) => {
let lower = *self_lower && *other_lower;
let upper = *self_upper && *other_upper;
Ok(Interval {
lower: IntervalBound::new(ScalarValue::Boolean(Some(lower)), false),
upper: IntervalBound::new(ScalarValue::Boolean(Some(upper)), false),
})
}
_ => Err(DataFusionError::Internal(
"Incompatible types for logical conjunction".to_string(),
)),
}
}
pub(crate) fn intersect<T: Borrow<Interval>>(
&self,
other: T,
) -> Result<Option<Interval>> {
let rhs = other.borrow();
if (!self.lower.is_unbounded()
&& !rhs.upper.is_unbounded()
&& self.lower.value > rhs.upper.value)
|| (!self.upper.is_unbounded()
&& !rhs.lower.is_unbounded()
&& self.upper.value < rhs.lower.value)
{
return Ok(None);
}
let lower = IntervalBound::choose(&self.lower, &rhs.lower, max)?;
let upper = IntervalBound::choose(&self.upper, &rhs.upper, min)?;
let non_empty = lower.is_unbounded()
|| upper.is_unbounded()
|| lower.value != upper.value
|| (!lower.open && !upper.open);
Ok(non_empty.then_some(Interval::new(lower, upper)))
}
pub fn add<T: Borrow<Interval>>(&self, other: T) -> Result<Interval> {
let rhs = other.borrow();
Ok(Interval::new(
self.lower.add::<false, _>(&rhs.lower)?,
self.upper.add::<true, _>(&rhs.upper)?,
))
}
pub fn sub<T: Borrow<Interval>>(&self, other: T) -> Result<Interval> {
let rhs = other.borrow();
Ok(Interval::new(
self.lower.sub::<false, _>(&rhs.upper)?,
self.upper.sub::<true, _>(&rhs.lower)?,
))
}
pub const CERTAINLY_FALSE: Interval = Interval {
lower: IntervalBound::new(ScalarValue::Boolean(Some(false)), false),
upper: IntervalBound::new(ScalarValue::Boolean(Some(false)), false),
};
pub const UNCERTAIN: Interval = Interval {
lower: IntervalBound::new(ScalarValue::Boolean(Some(false)), false),
upper: IntervalBound::new(ScalarValue::Boolean(Some(true)), false),
};
pub const CERTAINLY_TRUE: Interval = Interval {
lower: IntervalBound::new(ScalarValue::Boolean(Some(true)), false),
upper: IntervalBound::new(ScalarValue::Boolean(Some(true)), false),
};
}
pub fn is_operator_supported(op: &Operator) -> bool {
matches!(
op,
&Operator::Plus
| &Operator::Minus
| &Operator::And
| &Operator::Gt
| &Operator::GtEq
| &Operator::Lt
| &Operator::LtEq
)
}
pub fn is_datatype_supported(data_type: &DataType) -> bool {
matches!(
data_type,
&DataType::Int64
| &DataType::Int32
| &DataType::Int16
| &DataType::Int8
| &DataType::UInt64
| &DataType::UInt32
| &DataType::UInt16
| &DataType::UInt8
| &DataType::Float64
| &DataType::Float32
)
}
pub fn apply_operator(op: &Operator, lhs: &Interval, rhs: &Interval) -> Result<Interval> {
match *op {
Operator::Eq => Ok(lhs.equal(rhs)),
Operator::Gt => Ok(lhs.gt(rhs)),
Operator::GtEq => Ok(lhs.gt_eq(rhs)),
Operator::Lt => Ok(lhs.lt(rhs)),
Operator::LtEq => Ok(lhs.lt_eq(rhs)),
Operator::And => lhs.and(rhs),
Operator::Plus => lhs.add(rhs),
Operator::Minus => lhs.sub(rhs),
_ => Ok(Interval::default()),
}
}
fn cast_scalar_value(
value: &ScalarValue,
data_type: &DataType,
cast_options: &CastOptions,
) -> Result<ScalarValue> {
let cast_array = cast_with_options(&value.to_array(), data_type, cast_options)?;
ScalarValue::try_from_array(&cast_array, 0)
}
#[cfg(test)]
mod tests {
use crate::intervals::{Interval, IntervalBound};
use datafusion_common::{Result, ScalarValue};
use ScalarValue::Boolean;
fn open_open<T>(lower: Option<T>, upper: Option<T>) -> Interval
where
ScalarValue: From<Option<T>>,
{
Interval::make(lower, upper, (true, true))
}
fn open_closed<T>(lower: Option<T>, upper: Option<T>) -> Interval
where
ScalarValue: From<Option<T>>,
{
Interval::make(lower, upper, (true, false))
}
fn closed_open<T>(lower: Option<T>, upper: Option<T>) -> Interval
where
ScalarValue: From<Option<T>>,
{
Interval::make(lower, upper, (false, true))
}
fn closed_closed<T>(lower: Option<T>, upper: Option<T>) -> Interval
where
ScalarValue: From<Option<T>>,
{
Interval::make(lower, upper, (false, false))
}
#[test]
fn intersect_test() -> Result<()> {
let possible_cases = vec![
(Some(1000_i64), None, None, None, Some(1000_i64), None),
(None, Some(1000_i64), None, None, None, Some(1000_i64)),
(None, None, Some(1000_i64), None, Some(1000_i64), None),
(None, None, None, Some(1000_i64), None, Some(1000_i64)),
(
Some(1000_i64),
None,
Some(1000_i64),
None,
Some(1000_i64),
None,
),
(
None,
Some(1000_i64),
Some(999_i64),
Some(1002_i64),
Some(999_i64),
Some(1000_i64),
),
(None, None, None, None, None, None),
];
for case in possible_cases {
assert_eq!(
open_open(case.0, case.1).intersect(open_open(case.2, case.3))?,
Some(open_open(case.4, case.5))
)
}
let empty_cases = vec![
(None, Some(1000_i64), Some(1001_i64), None),
(Some(1001_i64), None, None, Some(1000_i64)),
(None, Some(1000_i64), Some(1001_i64), Some(1002_i64)),
(Some(1001_i64), Some(1002_i64), None, Some(1000_i64)),
];
for case in empty_cases {
assert_eq!(
open_open(case.0, case.1).intersect(open_open(case.2, case.3))?,
None
)
}
Ok(())
}
#[test]
fn gt_test() {
let cases = vec![
(Some(1000_i64), None, None, None, false, true),
(None, Some(1000_i64), None, None, false, true),
(None, None, Some(1000_i64), None, false, true),
(None, None, None, Some(1000_i64), false, true),
(None, Some(1000_i64), Some(1000_i64), None, false, false),
(None, Some(1000_i64), Some(1001_i64), None, false, false),
(Some(1000_i64), None, Some(1000_i64), None, false, true),
(
None,
Some(1000_i64),
Some(1001_i64),
Some(1002_i64),
false,
false,
),
(
None,
Some(1000_i64),
Some(999_i64),
Some(1002_i64),
false,
true,
),
(
Some(1002_i64),
None,
Some(999_i64),
Some(1002_i64),
true,
true,
),
(
Some(1003_i64),
None,
Some(999_i64),
Some(1002_i64),
true,
true,
),
(None, None, None, None, false, true),
];
for case in cases {
assert_eq!(
open_open(case.0, case.1).gt(open_open(case.2, case.3)),
closed_closed(Some(case.4), Some(case.5))
);
}
}
#[test]
fn lt_test() {
let cases = vec![
(Some(1000_i64), None, None, None, false, true),
(None, Some(1000_i64), None, None, false, true),
(None, None, Some(1000_i64), None, false, true),
(None, None, None, Some(1000_i64), false, true),
(None, Some(1000_i64), Some(1000_i64), None, true, true),
(None, Some(1000_i64), Some(1001_i64), None, true, true),
(Some(1000_i64), None, Some(1000_i64), None, false, true),
(
None,
Some(1000_i64),
Some(1001_i64),
Some(1002_i64),
true,
true,
),
(
None,
Some(1000_i64),
Some(999_i64),
Some(1002_i64),
false,
true,
),
(None, None, None, None, false, true),
];
for case in cases {
assert_eq!(
open_open(case.0, case.1).lt(open_open(case.2, case.3)),
closed_closed(Some(case.4), Some(case.5))
);
}
}
#[test]
fn and_test() -> Result<()> {
let cases = vec![
(false, true, false, false, false, false),
(false, false, false, true, false, false),
(false, true, false, true, false, true),
(false, true, true, true, false, true),
(false, false, false, false, false, false),
(true, true, true, true, true, true),
];
for case in cases {
assert_eq!(
open_open(Some(case.0), Some(case.1))
.and(open_open(Some(case.2), Some(case.3)))?,
open_open(Some(case.4), Some(case.5))
);
}
Ok(())
}
#[test]
fn add_test() -> Result<()> {
let cases = vec![
(Some(1000_i64), None, None, None, None, None),
(None, Some(1000_i64), None, None, None, None),
(None, None, Some(1000_i64), None, None, None),
(None, None, None, Some(1000_i64), None, None),
(
Some(1000_i64),
None,
Some(1000_i64),
None,
Some(2000_i64),
None,
),
(
None,
Some(1000_i64),
Some(999_i64),
Some(1002_i64),
None,
Some(2002_i64),
),
(None, Some(1000_i64), Some(1000_i64), None, None, None),
(
Some(2001_i64),
Some(1_i64),
Some(1005_i64),
Some(-999_i64),
Some(3006_i64),
Some(-998_i64),
),
(None, None, None, None, None, None),
];
for case in cases {
assert_eq!(
open_open(case.0, case.1).add(open_open(case.2, case.3))?,
open_open(case.4, case.5)
);
}
Ok(())
}
#[test]
fn sub_test() -> Result<()> {
let cases = vec![
(Some(1000_i64), None, None, None, None, None),
(None, Some(1000_i64), None, None, None, None),
(None, None, Some(1000_i64), None, None, None),
(None, None, None, Some(1000_i64), None, None),
(Some(1000_i64), None, Some(1000_i64), None, None, None),
(
None,
Some(1000_i64),
Some(999_i64),
Some(1002_i64),
None,
Some(1_i64),
),
(
None,
Some(1000_i64),
Some(1000_i64),
None,
None,
Some(0_i64),
),
(
Some(2001_i64),
Some(1000_i64),
Some(1005),
Some(999_i64),
Some(1002_i64),
Some(-5_i64),
),
(None, None, None, None, None, None),
];
for case in cases {
assert_eq!(
open_open(case.0, case.1).sub(open_open(case.2, case.3))?,
open_open(case.4, case.5)
);
}
Ok(())
}
#[test]
fn sub_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
closed_open(Some(200_i64), None),
open_closed(None, Some(0_i64)),
),
(
closed_open(Some(100_i64), Some(200_i64)),
open_closed(Some(300_i64), Some(150_i64)),
closed_open(Some(-50_i64), Some(-100_i64)),
),
(
closed_open(Some(100_i64), Some(200_i64)),
open_open(Some(200_i64), None),
open_open(None, Some(0_i64)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
closed_closed(Some(11_i64), Some(11_i64)),
closed_closed(Some(-10_i64), Some(-10_i64)),
),
];
for case in cases {
assert_eq!(case.0.sub(case.1)?, case.2)
}
Ok(())
}
#[test]
fn add_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(200_i64)),
open_closed(None, Some(400_i64)),
),
(
closed_open(Some(100_i64), Some(200_i64)),
closed_open(Some(-300_i64), Some(150_i64)),
closed_open(Some(-200_i64), Some(350_i64)),
),
(
closed_open(Some(100_i64), Some(200_i64)),
open_open(Some(200_i64), None),
open_open(Some(300_i64), None),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
closed_closed(Some(11_i64), Some(11_i64)),
closed_closed(Some(12_i64), Some(12_i64)),
),
];
for case in cases {
assert_eq!(case.0.add(case.1)?, case.2)
}
Ok(())
}
#[test]
fn lt_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(100_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(100_i64), Some(200_i64)),
open_open(None, Some(100_i64)),
closed_closed(Some(false), Some(false)),
),
(
open_open(Some(100_i64), Some(200_i64)),
closed_closed(Some(0_i64), Some(100_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_closed(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
open_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
];
for case in cases {
assert_eq!(case.0.lt(case.1), case.2)
}
Ok(())
}
#[test]
fn gt_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(100_i64)),
closed_closed(Some(false), Some(true)),
),
(
closed_closed(Some(100_i64), Some(200_i64)),
open_open(None, Some(100_i64)),
closed_closed(Some(true), Some(true)),
),
(
open_open(Some(100_i64), Some(200_i64)),
closed_closed(Some(0_i64), Some(100_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_closed(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(true)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
open_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(false)),
),
];
for case in cases {
assert_eq!(case.0.gt(case.1), case.2)
}
Ok(())
}
#[test]
fn lt_eq_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(100_i64)),
closed_closed(Some(false), Some(true)),
),
(
closed_closed(Some(100_i64), Some(200_i64)),
open_open(None, Some(100_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_closed(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(true)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(false)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
open_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
];
for case in cases {
assert_eq!(case.0.lt_eq(case.1), case.2)
}
Ok(())
}
#[test]
fn gt_eq_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(100_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(100_i64), Some(200_i64)),
open_open(None, Some(100_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_closed(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(true), Some(true)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
closed_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(true)),
),
(
closed_closed(Some(1_i64), Some(1_i64)),
open_open(Some(1_i64), Some(2_i64)),
closed_closed(Some(false), Some(false)),
),
];
for case in cases {
assert_eq!(case.0.gt_eq(case.1), case.2)
}
Ok(())
}
#[test]
fn intersect_test_various_bounds() -> Result<()> {
let cases = vec![
(
closed_closed(Some(100_i64), Some(200_i64)),
open_closed(None, Some(100_i64)),
Some(closed_closed(Some(100_i64), Some(100_i64))),
),
(
closed_closed(Some(100_i64), Some(200_i64)),
open_open(None, Some(100_i64)),
None,
),
(
open_open(Some(100_i64), Some(200_i64)),
closed_closed(Some(0_i64), Some(100_i64)),
None,
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_closed(Some(1_i64), Some(2_i64)),
Some(closed_closed(Some(2_i64), Some(2_i64))),
),
(
closed_closed(Some(2_i64), Some(2_i64)),
closed_open(Some(1_i64), Some(2_i64)),
None,
),
(
closed_closed(Some(1_i64), Some(1_i64)),
open_open(Some(1_i64), Some(2_i64)),
None,
),
(
closed_closed(Some(1_i64), Some(3_i64)),
open_open(Some(1_i64), Some(2_i64)),
Some(open_open(Some(1_i64), Some(2_i64))),
),
];
for case in cases {
assert_eq!(case.0.intersect(case.1)?, case.2)
}
Ok(())
}
#[test]
fn non_standard_interval_constructs() {
let cases = vec![
(
IntervalBound::new(Boolean(None), true),
IntervalBound::new(Boolean(Some(true)), false),
closed_closed(Some(false), Some(true)),
),
(
IntervalBound::new(Boolean(None), true),
IntervalBound::new(Boolean(Some(true)), true),
closed_closed(Some(false), Some(false)),
),
(
IntervalBound::new(Boolean(Some(false)), false),
IntervalBound::new(Boolean(None), true),
closed_closed(Some(false), Some(true)),
),
(
IntervalBound::new(Boolean(Some(true)), false),
IntervalBound::new(Boolean(None), true),
closed_closed(Some(true), Some(true)),
),
(
IntervalBound::new(Boolean(None), true),
IntervalBound::new(Boolean(None), true),
closed_closed(Some(false), Some(true)),
),
(
IntervalBound::new(Boolean(Some(false)), true),
IntervalBound::new(Boolean(None), true),
closed_closed(Some(true), Some(true)),
),
];
for case in cases {
assert_eq!(Interval::new(case.0, case.1), case.2)
}
}
macro_rules! capture_mode_change {
($TYPE:ty) => {
paste::item! {
capture_mode_change_helper!([<capture_mode_change_ $TYPE>],
[<create_interval_ $TYPE>],
$TYPE);
}
};
}
macro_rules! capture_mode_change_helper {
($TEST_FN_NAME:ident, $CREATE_FN_NAME:ident, $TYPE:ty) => {
fn $CREATE_FN_NAME(lower: $TYPE, upper: $TYPE) -> Interval {
Interval::make(Some(lower as $TYPE), Some(upper as $TYPE), (true, true))
}
fn $TEST_FN_NAME(input: ($TYPE, $TYPE), expect_low: bool, expect_high: bool) {
assert!(expect_low || expect_high);
let interval1 = $CREATE_FN_NAME(input.0, input.0);
let interval2 = $CREATE_FN_NAME(input.1, input.1);
let result = interval1.add(&interval2).unwrap();
let without_fe = $CREATE_FN_NAME(input.0 + input.1, input.0 + input.1);
assert!(
(!expect_low || result.lower.value < without_fe.lower.value)
&& (!expect_high || result.upper.value > without_fe.upper.value)
);
}
};
}
capture_mode_change!(f32);
capture_mode_change!(f64);
#[cfg(all(
any(target_arch = "x86_64", target_arch = "aarch64"),
not(target_os = "windows")
))]
#[test]
fn test_add_intervals_lower_affected_f32() {
let lower = f32::from_bits(1073741887); let upper = f32::from_bits(1098907651); capture_mode_change_f32((lower, upper), true, false);
let lower = f32::from_bits(1072693248); let upper = f32::from_bits(715827883); capture_mode_change_f32((lower, upper), false, true);
let lower = 1.0; let upper = 0.3; capture_mode_change_f64((lower, upper), true, false);
let lower = 1.4999999999999998; let upper = 0.000_000_000_000_000_022_044_604_925_031_31; capture_mode_change_f64((lower, upper), false, true);
}
#[cfg(any(
not(any(target_arch = "x86_64", target_arch = "aarch64")),
target_os = "windows"
))]
#[test]
fn test_next_impl_add_intervals_f64() {
let lower = 1.5;
let upper = 1.5;
capture_mode_change_f64((lower, upper), true, true);
let lower = 1.5;
let upper = 1.5;
capture_mode_change_f32((lower, upper), true, true);
}
}