Skip to content

Commit

Permalink
Bug/582 float nan inf (#588)
Browse files Browse the repository at this point in the history
exclude infinity from Float and Double wrappers
  • Loading branch information
aannleax authored Jan 13, 2025
2 parents 3b250d7 + ff94d26 commit 95057ad
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 82 deletions.
3 changes: 0 additions & 3 deletions nemo-physical/src/datatypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ pub use double::Double;
/// Module for defining [Float]
pub mod float;
pub use float::Float;
/// Module for defining [FloatIsNaN]
pub(crate) mod float_is_nan;
pub(crate) use float_is_nan::FloatIsNaN;
/// Module for defining [Ring]
pub(crate) mod ring;
pub(crate) use ring::Ring;
Expand Down
62 changes: 36 additions & 26 deletions nemo-physical/src/datatypes/double.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,59 @@
use super::run_length_encodable::FloatingStep;
use super::{FloatIsNaN, FloorToUsize, RunLengthEncodable};
use crate::error::{Error, ReadingError};
use crate::function::definitions::numeric::traits::{CheckedPow, CheckedSquareRoot};
use num::traits::CheckedNeg;
use num::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, One, Zero};
use std::cmp::Ordering;
use std::fmt;
use std::iter::{Product, Sum};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
//! This module defines a wrapper type [Double] for [f64] that excludes NaN and infinity.
use std::{
cmp::Ordering,
fmt,
iter::{Product, Sum},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign},
};

use num::{
traits::CheckedNeg, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive,
One, Zero,
};

use crate::{
error::{Error, ReadingError},
function::definitions::numeric::traits::{CheckedPow, CheckedSquareRoot},
};

use super::{run_length_encodable::FloatingStep, FloorToUsize, RunLengthEncodable};

#[cfg(test)]
use quickcheck::{Arbitrary, Gen};

/// Wrapper for [f64`] that does not allow [`f64::NAN] values.
/// Wrapper for [f64] that excludes [f64::NAN] and infinite values
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Double(f64);

impl Double {
/// Wraps the given [f64]-`value` as a value over [Double].
///
/// # Errors
/// The given `value` is [f64::NAN].
/// Returns an error if `value` is [f32::NAN] or infinite.
pub fn new(value: f64) -> Result<Self, ReadingError> {
if value.is_nan() {
return Err(FloatIsNaN.into());
if !value.is_finite() {
return Err(ReadingError::InvalidFloat);
}

Ok(Self(value))
}

/// Wraps the given [f64]-`value`, that is a number, as a value over [Double].
/// Wraps the given [f64]-`value` as a value over [Double].
///
/// # Panics
/// The given `value` is [f64::NAN].
/// Panics if `value` is [f64::NAN] or not finite.
pub fn from_number(value: f64) -> Self {
if value.is_nan() {
panic!("The provided value is not a number (NaN)!")
if !value.is_finite() {
panic!("floating point values must be finite")
}

Self(value)
}

/// Computes the absolute value.
pub(crate) fn abs(self) -> Self {
Double::new(self.0.abs()).expect("Taking the absolute value cannot result in NaN")
Double::new(self.0.abs()).expect("operation returns valid float")
}

/// Returns the logarithm of the number with respect to an arbitrary base.
Expand All @@ -53,34 +63,34 @@ impl Double {

/// Computes the sine of a number (in radians).
pub(crate) fn sin(self) -> Self {
Double::new(self.0.sin()).expect("Operation does not result in NaN")
Double::new(self.0.sin()).expect("operation returns valid float")
}

/// Computes the cosine of a number (in radians).
pub(crate) fn cos(self) -> Self {
Double::new(self.0.cos()).expect("Operation does not result in NaN")
Double::new(self.0.cos()).expect("operation returns valid float")
}

/// Computes the tangent of a number (in radians).
pub(crate) fn tan(self) -> Self {
Double::new(self.0.tan()).expect("Operation does not result in NaN")
Double::new(self.0.tan()).expect("operation returns valid float")
}

/// Returns the nearest integer to `self`.
/// If a value is half-way between two integers, round away from 0.0.
pub(crate) fn round(self) -> Self {
Double::new(self.0.round()).expect("Operation does not result in NaN")
Double::new(self.0.round()).expect("operation returns valid float")
}

/// Returns the nearest integer to `self`.
/// If a value is half-way between two integers, round away from 0.0.
pub(crate) fn ceil(self) -> Self {
Double::new(self.0.ceil()).expect("Operation does not result in NaN")
Double::new(self.0.ceil()).expect("operation returns valid float")
}

/// Returns the largest integer less than or equal to `self`.
pub(crate) fn floor(self) -> Self {
Double::new(self.0.floor()).expect("Operation does not result in NaN")
Double::new(self.0.floor()).expect("operation returns valid float")
}
}

Expand Down Expand Up @@ -294,7 +304,7 @@ impl Bounded for Double {
impl Arbitrary for Double {
fn arbitrary(g: &mut Gen) -> Self {
let mut value = f64::arbitrary(g);
while value.is_nan() {
while !value.is_finite() {
value = f64::arbitrary(g);
}

Expand Down
73 changes: 41 additions & 32 deletions nemo-physical/src/datatypes/float.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,58 @@
use super::run_length_encodable::FloatingStep;
use super::{FloatIsNaN, FloorToUsize, RunLengthEncodable};
use crate::error::Error;
use crate::function::definitions::numeric::traits::{CheckedPow, CheckedSquareRoot};
use num::traits::CheckedNeg;
use num::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, One, Zero};
use std::cmp::Ordering;
use std::fmt;
use std::iter::{Product, Sum};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
//! This module defines a wrapper type [Float] for [f32] that excludes NaN and infinity.
use std::{
cmp::Ordering,
fmt,
iter::{Product, Sum},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign},
};

use num::{
traits::CheckedNeg, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive,
One, Zero,
};

use super::{run_length_encodable::FloatingStep, FloorToUsize, RunLengthEncodable};
use crate::{
error::{Error, ReadingError},
function::definitions::numeric::traits::{CheckedPow, CheckedSquareRoot},
};

#[cfg(test)]
use quickcheck::{Arbitrary, Gen};

/// Wrapper for [f32`] that does not allow [`f32::NAN] values.
/// Wrapper for [f32] that excludes [f32::NAN] and infinite values
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Float(f32);

impl Float {
/// Wraps the given [f32]-`value` as a value over [Float].
///
/// # Errors
/// The given `value` is [f32::NAN].
pub fn new(value: f32) -> Result<Self, Error> {
if value.is_nan() {
return Err(Error::ReadingError(FloatIsNaN.into()));
/// Returns an error if `value` is [f32::NAN] or infinite.
pub fn new(value: f32) -> Result<Self, ReadingError> {
if !value.is_finite() {
return Err(ReadingError::InvalidFloat);
}

Ok(Float(value))
}

/// Wraps the given [f32]-`value`, that is a number, as a value over [Float].
/// Wraps the given [f32]-`value` as a value over [Float].
///
/// # Panics
/// The given `value` is [f32::NAN].
/// Panics if `value` is [f32::NAN] or not finite.
pub fn from_number(value: f32) -> Self {
if value.is_nan() {
panic!("The provided value is not a number (NaN)!")
if !value.is_finite() {
panic!("floating point values must be finite")
}

Float(value)
}

/// Computes the absolute value.
pub(crate) fn abs(self) -> Self {
Float::new(self.0.abs()).expect("Taking the absolute value cannot result in NaN")
Float::new(self.0.abs()).expect("operation returns valid float")
}

/// Returns the logarithm of the number with respect to an arbitrary base.
Expand All @@ -53,33 +62,33 @@ impl Float {

/// Computes the sine of a number (in radians).
pub(crate) fn sin(self) -> Self {
Float::new(self.0.sin()).expect("Operation does not result in NaN")
Float::new(self.0.sin()).expect("operation returns valid float")
}

/// Computes the cosine of a number (in radians).
pub(crate) fn cos(self) -> Self {
Float::new(self.0.cos()).expect("Operation does not result in NaN")
Float::new(self.0.cos()).expect("operation returns valid float")
}

/// Computes the tangent of a number (in radians).
pub(crate) fn tan(self) -> Self {
Float::new(self.0.tan()).expect("Operation does not result in NaN")
Float::new(self.0.tan()).expect("operation returns valid float")
}

/// Returns the nearest integer to `self`.
/// If a value is half-way between two integers, round away from 0.0.
pub(crate) fn round(self) -> Self {
Float::new(self.0.round()).expect("Operation does not result in NaN")
Float::new(self.0.round()).expect("operation returns valid float")
}

/// Returns the smallest integer greater than or equal to `self`.
pub(crate) fn ceil(self) -> Self {
Float::new(self.0.ceil()).expect("Operation does not result in NaN")
Float::new(self.0.ceil()).expect("operation returns valid float")
}

/// Returns the largest integer less than or equal to `self`.
pub(crate) fn floor(self) -> Self {
Float::new(self.0.floor()).expect("Operation does not result in NaN")
Float::new(self.0.floor()).expect("operation returns valid float")
}
}

Expand All @@ -95,7 +104,7 @@ impl Ord for Float {
fn cmp(&self, other: &Self) -> Ordering {
self.0
.partial_cmp(&other.0)
.expect("Comparison can only fail on NaN values which have been forbidden in this type")
.expect("comparison can only fail on NaN values which have been forbidden in this type")
}
}

Expand Down Expand Up @@ -170,7 +179,7 @@ impl fmt::Display for Float {
}

impl TryFrom<f32> for Float {
type Error = Error;
type Error = ReadingError;

fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::new(value)
Expand All @@ -187,9 +196,9 @@ impl TryFrom<usize> for Float {
type Error = Error;

fn try_from(value: usize) -> Result<Self, Self::Error> {
f32::from_usize(value)
.ok_or(Error::UsizeToFloatingPointValue(value))
.and_then(Float::new)
let res = f32::from_usize(value).ok_or(Error::UsizeToFloatingPointValue(value))?;

Ok(Float::new(res)?)
}
}

Expand Down Expand Up @@ -293,7 +302,7 @@ impl Bounded for Float {
impl Arbitrary for Float {
fn arbitrary(g: &mut Gen) -> Self {
let mut value = f32::arbitrary(g);
while value.is_nan() {
while !value.is_finite() {
value = f32::arbitrary(g);
}

Expand Down
18 changes: 0 additions & 18 deletions nemo-physical/src/datatypes/float_is_nan.rs

This file was deleted.

6 changes: 3 additions & 3 deletions nemo-physical/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{convert::Infallible, fmt::Display};

use thiserror::Error;

use crate::{datatypes::FloatIsNaN, resource::Resource};
use crate::resource::Resource;

/// Trait that can be used by external libraries extending Nemo to communicate a error during reading
pub trait ExternalReadingError: Display + std::fmt::Debug {}
Expand All @@ -17,8 +17,8 @@ pub enum ReadingError {
#[error(transparent)]
ExternalError(#[from] Box<dyn std::error::Error>),
/// Error from trying to use a floating point number that is NaN
#[error(transparent)]
FloatIsNaN(#[from] FloatIsNaN),
#[error("floating point values must be finite and not NaN")]
InvalidFloat,
/// Error occurred during parsing of Int values
#[error(transparent)]
ParseInt(#[from] std::num::ParseIntError),
Expand Down
11 changes: 11 additions & 0 deletions resources/testcases/regression/builtin/float-nan-inf/run.rls
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%! This test case is based on https://github.com/knowsys/nemo/issues/582.
%! A crash was caused by not correctly checking for NaN and infinity when
%! doing computations with floating point numbers.

big(1.7976931348623157E308) .
small(1.0E-32) .
small(0.0) .

result(?a / ?b) :- big(?a), small(?b) .

@export result :- csv {} .
Empty file.

0 comments on commit 95057ad

Please sign in to comment.