approximate-equality-1.1.0.2: Newtype wrappers for approximate equality

Safe HaskellSafe-Inferred

Data.Eq.Approximate

Contents

Description

The purpose of this module is to provide newtype wrapper that allows one to effectively override the equality operator of a value so that it is approximate rather than exact. For example, the type

 type ApproximateDouble = AbsolutelyApproximateValue (Digits Five) Double

defines an alias for a wrapper containing Doubles such that two doubles are equal if they are equal to within five decimals of accuracy; for example, we have that

 1 == (1+10^^(-6) :: ApproximateDouble)

evaluates to True. Note that we did not need to wrap the value 1+10^^(-6) since AbsolutelyApproximateValue is an instance of Num. For convenience, Num as well as many other of the numerical classes such as Real and Floating have all been derived for the wrappers defined in this package so that one can conveniently use the wrapped values in the same way as one would use the values themselves.

Two kinds of wrappers are provided by this package.

  • AbsolutelyApproximateValue wraps values that are considered to be equal if their absolute difference falls within the specified tolerance.
  • RelativelyApproximateValue wraps values that are considered to be equal if the absolute difference between the values divided by the average of the absolute values is within the given relative tolerance, or if the absolute value of both values falls within the zero tolerance; the latter case is checked because otherwise no value, no matter how small, would be approximately equal to zero.

The tolerance is specified through a type annotation. One can use any annotation that one wishes as long as the type is an instance of AbsoluteTolerance (for absolute tolerances) and/or RelativeTolerance and ZeroTolerance (for relative tolerances). For convenience, this package provides the type Digits that allows one to specify the tolerance in terms of the number of digits, making use of type-level natural numbers. The annotation Digits n sets the tolerance to 10^-n, so that in the case of the absolute tolerance and the zero tolerance n is the number of decimal places that numbers have to match to be equal to respectively either each other or to zero, and in the case of relative tolerance n is (roughly) the number of leading digits that two numbers have to match in order to be equal to each other.

Synopsis

Type wrappers

newtype AbsolutelyApproximateValue absolute_tolerance value Source

The newtype AbsolutelyApproximateValue is a wrapper that can contain an arbitrary value tagged with a tolerance; two values are equal to each other if the absolute difference is less than or equal to this tolerance. The type annotation absolute_tolerance, which must be an instance of AbsoluteTolerance, specifies the tolerance. For convenience, one may specify the tolerance using the type Digits n where n is a type-level natural specifying the number of decimals in the tolerance (i.e., Digits Four specifies a tolerance of 0.0001).

It is recommended that one use this wrapper by creating aliases, such as

 type ApproximateDouble = AbsolutelyApproximateValue (Digits Five)
 wrapAD :: Double -> ApproximateDouble
 wrapAD = AbsolutelyApproximateValue
 unwrapAD :: ApproximateDouble -> Double
 unwrapAD = unwrapAbsolutelyApproximateValue

You can then replace the type Double in your code with the type alias ApproximateDouble to get the feature of approximate equality. Most of the time you will find that you do not need to use wrapping functions to construct wrapped values since AbsolutelyApproximateValue is an instance of whatever numerical types the wrapped value is, so that for example 1 + sqrt 2/3 is already a value of type ApproximateDouble without needing to be wrapped first.

Instances

Enum value => Enum (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value) => Eq (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Floating value) => Floating (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value) => Fractional (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value, Integral value) => Integral (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value) => Num (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value) => Ord (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value, Real value) => Real (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, RealFloat value) => RealFloat (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, RealFrac value) => RealFrac (AbsolutelyApproximateValue tolerance value) 
(AbsoluteTolerance tolerance, Ord value, Fractional value, Show value) => Show (AbsolutelyApproximateValue tolerance value) 

newtype RelativelyApproximateValue zero_tolerance relative_tolerance value Source

The newtype RelativelyApproximateValue is a wrapper that can contain an arbitrary value tagged with a zero tolerance and a relative tolerance; two values are equal to each other if their absolute values are both less than or equal to the zero tolerance, or if the absolute difference between them divided by the average of the absolute values is less than or equal to the relative tolerance.

The type annotation zero_tolerance, which must be an instance of ZeroTolerance, specifies the tolerance within which a value is considered to be equal to zero. For convenience, one may specify the tolerance using the type Digits n where n is a type-level natural specifying the number of decimals in the tolerance (i.e., Digits Four specifies a tolerance of 0.0001).

The type annotation relative_tolerance, which must be an instance of RelativeTolerance, specifies the relative tolerance within which two values that are not approximately equal to zero are considered to be equal to each other. For convenience, as with the zero tolerance, one may specify the relative tolerance using the type Digits n where n is a type-level natural specifying the number of decimals in the tolerance (i.e., Digits Four specifies a relative tolerance of 0.0001, so that two values are equal if they agree to the first four leading digits).

It is recommended that one use this wrapper by creating aliases, such as

 type ApproximateDouble = RelativelyApproximateValue (Digits Five) (Digits Five)
 wrapAD :: Double -> ApproximateDouble
 wrapAD = RelativelyApproximateValue
 unwrapAD :: ApproximateDouble -> Double
 unwrapAD = unwrapRelativelyApproximateValue

You can then replace the type Double in your code with the type alias ApproximateDouble to get the feature of approximate equality. Most of the time you will find that you do not need to use wrapping functions to construct wrapped values since RelativelyApproximateValue is an instance of whatever numerical types the wrapped value is, so that for example 1 + sqrt 2/3 is already a value of type ApproximateDouble without needing to be wrapped first.

Instances

Enum value => Enum (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value) => Eq (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Floating value) => Floating (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value) => Fractional (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value, Integral value) => Integral (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value) => Num (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value) => Ord (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value, Real value) => Real (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, RealFloat value) => RealFloat (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, RealFrac value) => RealFrac (RelativelyApproximateValue zerotol reltol value) 
(ZeroTolerance zerotol, RelativeTolerance reltol, Ord value, Fractional value, Show value) => Show (RelativelyApproximateValue zerotol reltol value) 

Classes for tolerance type annotations

The classes in this section are used to associate numerical tolerance information with the types that are used to annotate the type wrappers AbsolutelyApproximateValue and RelativelyApproximateValue. One typically should not need to use them since the annotation Digits n should cover most common cases.

One might consider hand-rolling instances in order to obtain faster code (since this will bypass the type-level natural numbers), however I have found in my own experience that using hand-rolled instances rather than Digits Five in my own program resulted in code that was one or two orders of magnitude slower than simply using Digits. This is not to claim that hand-rolled instances can never be faster than using Digits, but one should be careful about trying this without checking whether there really is an improvement. In general, Digits seems to be fast enough in the common case that hand-rolling instances for speed is unlikely to be worth the trouble.

Absolute tolerance

class AbsoluteTolerance absolute_tolerance whereSource

The class AbsoluteTolerance is used to define the absolute tolerances associated with types that will be used as absolute tolerance type annotations in AbsolutelyApproximateValue.

Methods

absoluteToleranceOf :: Fractional value => AbsolutelyApproximateValue absolute_tolerance value -> valueSource

This method retrieves the numerical absolute tolerance associated with the type; it should be a constant function.

getAbsoluteTolerance :: AbsolutelyApproximateValue absolute_tolerance value -> absolute_toleranceSource

This is a convenience (constant) function for extracting the relative tolerance type annotation from AbsolutelyApproximateValue; it returns the value undefined, so don't try to evaluate the result.

Relative tolerance

class RelativeTolerance relative_tolerance whereSource

The class RelativeTolerance is used to define the relative tolerances associated with types that will be used as relative tolerance type annotations in RelativelyApproximateValue.

Methods

relativeToleranceOf :: Fractional value => RelativelyApproximateValue zero_tolerance relative_tolerance value -> valueSource

This method retrieves the numerical relative tolerance associated with the type; it should be a constant function.

getRelativeTolerance :: RelativelyApproximateValue zero_tolerance relative_tolerance value -> relative_toleranceSource

This is a convenience (constant) function for extracting the relative tolerance type annotation from RelativelyApproximateValue; it returns the value undefined, so don't try to evaluate the result.

Zero tolerance

class ZeroTolerance zero_tolerance whereSource

The class ZeroTolerance is used to define the numerical zero tolerances associated with types that will be used as zero tolerance type annotations in RelativelyApproximateValue.

Methods

zeroToleranceOf :: Fractional value => RelativelyApproximateValue zero_tolerance relative_tolerance value -> valueSource

This method retrieves the numerical zero tolerance associated with the type; it should be a constant function.

Instances

getZeroTolerance :: RelativelyApproximateValue zero_tolerance relative_tolerance value -> zero_toleranceSource

This is a convenience (constant) function for extracting the zero tolerance type annotation from RelativelyApproximateValue; it returns the value undefined, so don't try to evaluate the result.

Tolerance annotations using Digits

data Digits n Source

Digits is a type constructor that can be used to specify tolerances using type-level natural numbers. Annotating a wrapper with the type Digits n specifies that the corresponding tolerance has a numerical value of 10^(-n).

unwrapDigits :: Digits n -> nSource

This is a convenience (constant) function for extracting the type-level natural number contained within the Digits type constructor; it returns the value undefined, so don't try to evaluate the result.

toleranceFromDigits :: NaturalNumber n => Fractional value => Digits n -> valueSource

This is a convenience (constant) function that computes the numerical tolerance specified by Digits n, which is 10^(-n).