From c93ce39567b73c56f11d4e731de102c532e3654d Mon Sep 17 00:00:00 2001 From: Liang-Chi Hsieh Date: Thu, 6 Oct 2022 10:45:23 -0700 Subject: [PATCH] Add NaN handling in dyn scalar comparison kernels (#2830) * Add NaN handling in dyn scalar comparison kernels * Use trait * Fix clippy * Prepare for simd and non-simd tests * Restore flight protocal files. * Use ArrowNativeTypeOp as type bound * Remove num::ToPrimitive addition --- arrow/benches/comparison_kernels.rs | 74 ++++-------- arrow/src/compute/kernels/comparison.rs | 153 +++++++++++++++++++++--- arrow/src/datatypes/native.rs | 60 +++++++++- 3 files changed, 217 insertions(+), 70 deletions(-) diff --git a/arrow/benches/comparison_kernels.rs b/arrow/benches/comparison_kernels.rs index 4ad139b879f..e2afa99fb2c 100644 --- a/arrow/benches/comparison_kernels.rs +++ b/arrow/benches/comparison_kernels.rs @@ -33,13 +33,6 @@ where eq(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_eq_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - eq_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_neq(arr_a: &PrimitiveArray, arr_b: &PrimitiveArray) where T: ArrowNumericType, @@ -47,13 +40,6 @@ where neq(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_neq_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - neq_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_lt(arr_a: &PrimitiveArray, arr_b: &PrimitiveArray) where T: ArrowNumericType, @@ -61,13 +47,6 @@ where lt(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_lt_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - lt_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_lt_eq(arr_a: &PrimitiveArray, arr_b: &PrimitiveArray) where T: ArrowNumericType, @@ -75,13 +54,6 @@ where lt_eq(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_lt_eq_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - lt_eq_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_gt(arr_a: &PrimitiveArray, arr_b: &PrimitiveArray) where T: ArrowNumericType, @@ -89,13 +61,6 @@ where gt(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_gt_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - gt_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_gt_eq(arr_a: &PrimitiveArray, arr_b: &PrimitiveArray) where T: ArrowNumericType, @@ -103,13 +68,6 @@ where gt_eq(criterion::black_box(arr_a), criterion::black_box(arr_b)).unwrap(); } -fn bench_gt_eq_scalar(arr_a: &PrimitiveArray, value_b: T::Native) -where - T: ArrowNumericType, -{ - gt_eq_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); -} - fn bench_like_utf8_scalar(arr_a: &StringArray, value_b: &str) { like_utf8_scalar(criterion::black_box(arr_a), criterion::black_box(value_b)).unwrap(); } @@ -164,39 +122,57 @@ fn add_benchmark(c: &mut Criterion) { c.bench_function("eq Float32", |b| b.iter(|| bench_eq(&arr_a, &arr_b))); c.bench_function("eq scalar Float32", |b| { - b.iter(|| bench_eq_scalar(&arr_a, 1.0)) + b.iter(|| { + eq_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("neq Float32", |b| b.iter(|| bench_neq(&arr_a, &arr_b))); c.bench_function("neq scalar Float32", |b| { - b.iter(|| bench_neq_scalar(&arr_a, 1.0)) + b.iter(|| { + neq_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("lt Float32", |b| b.iter(|| bench_lt(&arr_a, &arr_b))); c.bench_function("lt scalar Float32", |b| { - b.iter(|| bench_lt_scalar(&arr_a, 1.0)) + b.iter(|| { + lt_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("lt_eq Float32", |b| b.iter(|| bench_lt_eq(&arr_a, &arr_b))); c.bench_function("lt_eq scalar Float32", |b| { - b.iter(|| bench_lt_eq_scalar(&arr_a, 1.0)) + b.iter(|| { + lt_eq_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("gt Float32", |b| b.iter(|| bench_gt(&arr_a, &arr_b))); c.bench_function("gt scalar Float32", |b| { - b.iter(|| bench_gt_scalar(&arr_a, 1.0)) + b.iter(|| { + gt_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("gt_eq Float32", |b| b.iter(|| bench_gt_eq(&arr_a, &arr_b))); c.bench_function("gt_eq scalar Float32", |b| { - b.iter(|| bench_gt_eq_scalar(&arr_a, 1.0)) + b.iter(|| { + gt_eq_scalar(criterion::black_box(&arr_a), criterion::black_box(1.0)).unwrap() + }) }); c.bench_function("eq MonthDayNano", |b| { b.iter(|| bench_eq(&arr_month_day_nano_a, &arr_month_day_nano_b)) }); c.bench_function("eq scalar MonthDayNano", |b| { - b.iter(|| bench_eq_scalar(&arr_month_day_nano_a, 123)) + b.iter(|| { + eq_scalar( + criterion::black_box(&arr_month_day_nano_a), + criterion::black_box(123), + ) + .unwrap() + }) }); c.bench_function("like_utf8 scalar equals", |b| { diff --git a/arrow/src/compute/kernels/comparison.rs b/arrow/src/compute/kernels/comparison.rs index 1ea433150f0..d1d1e470e51 100644 --- a/arrow/src/compute/kernels/comparison.rs +++ b/arrow/src/compute/kernels/comparison.rs @@ -27,12 +27,13 @@ use crate::array::*; use crate::buffer::{buffer_unary_not, Buffer, MutableBuffer}; use crate::compute::util::combine_option_bitmap; use crate::datatypes::{ - ArrowNativeType, ArrowNumericType, DataType, Date32Type, Date64Type, Float32Type, - Float64Type, Int16Type, Int32Type, Int64Type, Int8Type, IntervalDayTimeType, - IntervalMonthDayNanoType, IntervalUnit, IntervalYearMonthType, Time32MillisecondType, - Time32SecondType, Time64MicrosecondType, Time64NanosecondType, TimeUnit, - TimestampMicrosecondType, TimestampMillisecondType, TimestampNanosecondType, - TimestampSecondType, UInt16Type, UInt32Type, UInt64Type, UInt8Type, + native_op::ArrowNativeTypeOp, ArrowNativeType, ArrowNumericType, DataType, + Date32Type, Date64Type, Float32Type, Float64Type, Int16Type, Int32Type, Int64Type, + Int8Type, IntervalDayTimeType, IntervalMonthDayNanoType, IntervalUnit, + IntervalYearMonthType, Time32MillisecondType, Time32SecondType, + Time64MicrosecondType, Time64NanosecondType, TimeUnit, TimestampMicrosecondType, + TimestampMillisecondType, TimestampNanosecondType, TimestampSecondType, UInt16Type, + UInt32Type, UInt64Type, UInt8Type, }; #[allow(unused_imports)] use crate::downcast_dictionary_array; @@ -1328,7 +1329,12 @@ macro_rules! dyn_compare_utf8_scalar { } /// Perform `left == right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn eq_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -1342,7 +1348,12 @@ where } /// Perform `left < right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn lt_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -1356,7 +1367,12 @@ where } /// Perform `left <= right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn lt_eq_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -1370,7 +1386,12 @@ where } /// Perform `left > right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn gt_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -1384,7 +1405,12 @@ where } /// Perform `left >= right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn gt_eq_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -1398,7 +1424,12 @@ where } /// Perform `left != right` operation on an array and a numeric scalar -/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values +/// value. Supports PrimitiveArrays, and DictionaryArrays that have primitive values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn neq_dyn_scalar(left: &dyn Array, right: T) -> Result where T: num::ToPrimitive + std::fmt::Debug, @@ -3016,14 +3047,20 @@ where } /// Perform `left == right` operation on a [`PrimitiveArray`] and a scalar value. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn eq_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::eq, |a, b| a == b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a == right); + return compare_op_scalar(left, |a| a.is_eq(right)); } /// Applies an unary and infallible comparison function to a primitive array. @@ -3047,14 +3084,20 @@ where } /// Perform `left != right` operation on a [`PrimitiveArray`] and a scalar value. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn neq_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::ne, |a, b| a != b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a != right); + return compare_op_scalar(left, |a| a.is_ne(right)); } /// Perform `left < right` operation on two [`PrimitiveArray`]s. Null values are less than non-null @@ -3071,14 +3114,20 @@ where /// Perform `left < right` operation on a [`PrimitiveArray`] and a scalar value. /// Null values are less than non-null values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn lt_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::lt, |a, b| a < b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a < right); + return compare_op_scalar(left, |a| a.is_lt(right)); } /// Perform `left <= right` operation on two [`PrimitiveArray`]s. Null values are less than non-null @@ -3098,14 +3147,20 @@ where /// Perform `left <= right` operation on a [`PrimitiveArray`] and a scalar value. /// Null values are less than non-null values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn lt_eq_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::le, |a, b| a <= b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a <= right); + return compare_op_scalar(left, |a| a.is_le(right)); } /// Perform `left > right` operation on two [`PrimitiveArray`]s. Non-null values are greater than null @@ -3122,14 +3177,20 @@ where /// Perform `left > right` operation on a [`PrimitiveArray`] and a scalar value. /// Non-null values are greater than null values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn gt_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::gt, |a, b| a > b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a > right); + return compare_op_scalar(left, |a| a.is_gt(right)); } /// Perform `left >= right` operation on two [`PrimitiveArray`]s. Non-null values are greater than null @@ -3149,14 +3210,20 @@ where /// Perform `left >= right` operation on a [`PrimitiveArray`] and a scalar value. /// Non-null values are greater than null values. +/// +/// If `simd` feature flag is not enabled: +/// For floating values like f32 and f64, this comparison produces an ordering in accordance to +/// the totalOrder predicate as defined in the IEEE 754 (2008 revision) floating point standard. +/// Please refer to `f32::total_cmp` and `f64::total_cmp`. pub fn gt_eq_scalar(left: &PrimitiveArray, right: T::Native) -> Result where T: ArrowNumericType, + T::Native: ArrowNativeTypeOp, { #[cfg(feature = "simd")] return simd_compare_op_scalar(left, right, T::ge, |a, b| a >= b); #[cfg(not(feature = "simd"))] - return compare_op_scalar(left, |a| a >= right); + return compare_op_scalar(left, |a| a.is_ge(right)); } /// Checks if a [`GenericListArray`] contains a value in the [`PrimitiveArray`] @@ -5848,28 +5915,48 @@ mod tests { .into_iter() .map(Some) .collect(); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(false), Some(false), Some(false), Some(false)], + ); assert_eq!(eq_dyn_scalar(&array, f32::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(true), Some(true), Some(true), Some(true), Some(true)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(false), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(neq_dyn_scalar(&array, f32::NAN).unwrap(), expected); let array: Float64Array = vec![f64::NAN, 7.0, 8.0, 8.0, 10.0] .into_iter() .map(Some) .collect(); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(false), Some(false), Some(false), Some(false)], + ); assert_eq!(eq_dyn_scalar(&array, f64::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(true), Some(true), Some(true), Some(true), Some(true)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(false), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(neq_dyn_scalar(&array, f64::NAN).unwrap(), expected); } @@ -5879,28 +5966,48 @@ mod tests { .into_iter() .map(Some) .collect(); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(false), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(lt_dyn_scalar(&array, f32::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(lt_eq_dyn_scalar(&array, f32::NAN).unwrap(), expected); let array: Float64Array = vec![f64::NAN, 7.0, 8.0, 8.0, 10.0] .into_iter() .map(Some) .collect(); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(false), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(lt_dyn_scalar(&array, f64::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(true), Some(true), Some(true), Some(true)], + ); assert_eq!(lt_eq_dyn_scalar(&array, f64::NAN).unwrap(), expected); } @@ -5915,9 +6022,14 @@ mod tests { ); assert_eq!(gt_dyn_scalar(&array, f32::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(false), Some(false), Some(false), Some(false)], + ); assert_eq!(gt_eq_dyn_scalar(&array, f32::NAN).unwrap(), expected); let array: Float64Array = vec![f64::NAN, 7.0, 8.0, 8.0, 10.0] @@ -5929,9 +6041,14 @@ mod tests { ); assert_eq!(gt_dyn_scalar(&array, f64::NAN).unwrap(), expected); + #[cfg(feature = "simd")] let expected = BooleanArray::from( vec![Some(false), Some(false), Some(false), Some(false), Some(false)], ); + #[cfg(not(feature = "simd"))] + let expected = BooleanArray::from( + vec![Some(true), Some(false), Some(false), Some(false), Some(false)], + ); assert_eq!(gt_eq_dyn_scalar(&array, f64::NAN).unwrap(), expected); } diff --git a/arrow/src/datatypes/native.rs b/arrow/src/datatypes/native.rs index 654b939500a..374d0b950ea 100644 --- a/arrow/src/datatypes/native.rs +++ b/arrow/src/datatypes/native.rs @@ -94,6 +94,30 @@ pub(crate) mod native_op { fn mod_wrapping(self, rhs: Self) -> Self { self % rhs } + + fn is_eq(self, rhs: Self) -> bool { + self == rhs + } + + fn is_ne(self, rhs: Self) -> bool { + self != rhs + } + + fn is_lt(self, rhs: Self) -> bool { + self < rhs + } + + fn is_le(self, rhs: Self) -> bool { + self <= rhs + } + + fn is_gt(self, rhs: Self) -> bool { + self > rhs + } + + fn is_ge(self, rhs: Self) -> bool { + self >= rhs + } } } @@ -186,6 +210,36 @@ native_type_op!(u16); native_type_op!(u32); native_type_op!(u64); -impl native_op::ArrowNativeTypeOp for f16 {} -impl native_op::ArrowNativeTypeOp for f32 {} -impl native_op::ArrowNativeTypeOp for f64 {} +macro_rules! native_type_float_op { + ($t:tt) => { + impl native_op::ArrowNativeTypeOp for $t { + fn is_eq(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_eq() + } + + fn is_ne(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_ne() + } + + fn is_lt(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_lt() + } + + fn is_le(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_le() + } + + fn is_gt(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_gt() + } + + fn is_ge(self, rhs: Self) -> bool { + self.total_cmp(&rhs).is_ge() + } + } + }; +} + +native_type_float_op!(f16); +native_type_float_op!(f32); +native_type_float_op!(f64);