packages feed

RoundingFiasco-0.1.0.0: source/Rounding.hs

{-|

This module provides various combinations of explicit floating point operations.

The are three supported rounding variants:

  * ceil (rounding towards positive infinity)

  * floor (rounding towards negative infinity)

  * truncate (rounding towards zero)

Operators:

  * `+` add

  * `-` subtract

  * `*` multiplicate

  * `/` divide

  * `sqrt` squareRoot

  * `id` converting numbers between formats

The floating point data types:

  * `Float` (single precision)

  * `Double` (double precision)

The behaviour for `NaN`s is very hardware dependent. In case an operation would result in a `NaN` the corresponding round-to-nearest-even-on-tie variant is used to get the result.

-}



{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ExtendedLiterals #-}



module Rounding

  -- * Arithmetic Signum

  ( i32_arithmic_signum_f64
  , i32_arithmic_signum_f64#
  , i32_arithmic_signum_f32
  , i32_arithmic_signum_f32#

  -- * Sign bit

  , i32_sign_bit_f64
  , i32_sign_bit_f64#
  , i32_sign_bit_f32
  , i32_sign_bit_f32#

  -- * Bit pattern neighbours

  , f64_successorIEEE
  , f64_successorIEEE#
  , f64_predecessorIEEE
  , f64_predecessorIEEE#

  , f32_successorIEEE
  , f32_successorIEEE#
  , f32_predecessorIEEE
  , f32_predecessorIEEE#

  -- * Ceil variants

  {-|

  Operators of the ceil variant operate as if they calculated the exact mathematic result. Then they take the minimum of all floating point numbers that are greater or equal to the exact result. Special care is taken for the sign of zero.

  Should the result from a plus or minus operation be zero the sign is positive.

  -}

  , f32_squareRoot_ceil
  , f32_add_ceil
  , f32_subtract_ceil
  , f32_multiplicate_ceil
  , f32_divide_ceil
  , f64_squareRoot_ceil
  , f64_add_ceil
  , f64_subtract_ceil
  , f64_multiplicate_ceil
  , f64_divide_ceil
  , f32_convert_i32_signed_ceil
  , f32_convert_i32_unsigned_ceil
  , f32_convert_i64_signed_ceil
  , f32_convert_i64_unsigned_ceil
  , f32_demote_f64_ceil
  , f64_convert_i32_signed_ceil
  , f64_convert_i32_unsigned_ceil
  , f64_convert_i64_signed_ceil
  , f64_convert_i64_unsigned_ceil
  , f64_promote_f32_ceil

  -- * Floor variants

  {-|

  The operators operate as if they calculate the exact mathematic result. Then they take the maximum of all floating point numbers that are less or equal to the exact result.

  Should the result from a plus or minus operation be zero the sign is negative.

  -}

  , f32_squareRoot_floor
  , f32_add_floor
  , f32_subtract_floor
  , f32_multiplicate_floor
  , f32_divide_floor
  , f64_squareRoot_floor
  , f64_add_floor
  , f64_subtract_floor
  , f64_multiplicate_floor
  , f64_divide_floor
  , f32_convert_i32_signed_floor
  , f32_convert_i32_unsigned_floor
  , f32_convert_i64_signed_floor
  , f32_convert_i64_unsigned_floor
  , f32_demote_f64_floor
  , f64_convert_i32_signed_floor
  , f64_convert_i32_unsigned_floor
  , f64_convert_i64_signed_floor
  , f64_convert_i64_unsigned_floor
  , f64_promote_f32_floor

  -- * Truncate variants

  {-|

  Operators of the truncate variant operate as if they calculated the exact result. Then they take the result from the floor variant in case the exact result is greater than zero. Should the exact result be zero or less the ceil variant is choosen.

  -}

  , f32_squareRoot_truncate
  , f32_add_truncate
  , f32_subtract_truncate
  , f32_multiplicate_truncate
  , f32_divide_truncate
  , f64_squareRoot_truncate
  , f64_add_truncate
  , f64_subtract_truncate
  , f64_multiplicate_truncate
  , f64_divide_truncate
  , f32_convert_i32_signed_truncate
  , f32_convert_i32_unsigned_truncate
  , f32_convert_i64_signed_truncate
  , f32_convert_i64_unsigned_truncate
  , f32_demote_f64_truncate
  , f64_convert_i32_signed_truncate
  , f64_convert_i32_unsigned_truncate
  , f64_convert_i64_signed_truncate
  , f64_convert_i64_unsigned_truncate
  , f64_promote_f32_truncate

  ) where



import GHC.Exts
import GHC.Float
import GHC.Int
import Data.Word


-- | There are three possible outputs:
--
-- prop> i32_arithmic_signum_f64 (-1.0) = -1
-- prop> i32_arithmic_signum_f64 (-0.0) = 0
-- prop> i32_arithmic_signum_f64 (0.0) = 0
-- prop> i32_arithmic_signum_f64 (1.0) = 1
--
-- @`NaN`@ values are mapped to @`0`@:
--
-- prop> i32_arithmic_signum_f64 (0/0) = 0
{-# INLINABLE i32_arithmic_signum_f64 #-}
i32_arithmic_signum_f64 :: Double -> Int32
i32_arithmic_signum_f64 (D# x) = I32# (intToInt32# (i32_arithmic_signum_f64# x))
{-# INLINABLE i32_arithmic_signum_f32 #-}
i32_arithmic_signum_f32 :: Float -> Int32
i32_arithmic_signum_f32 (F# x) = I32# (intToInt32# (i32_arithmic_signum_f32# x))

-- https://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c
{-# INLINABLE i32_arithmic_signum_f64# #-}
i32_arithmic_signum_f64# :: Double# -> Int#
i32_arithmic_signum_f64# x = (0.0## <## x) -# (x <## 0.0##)
{-# INLINABLE i32_arithmic_signum_f32# #-}
i32_arithmic_signum_f32# :: Float# -> Int#
i32_arithmic_signum_f32# x = (0.0# `ltFloat#` x) -# (x `ltFloat#` 0.0#)


-- | There are two possible outputs:
--
-- prop> i32_sign_bit_f64 (-1.0) = 1
-- prop> i32_sign_bit_f64 (-0.0) = 1
-- prop> i32_sign_bit_f64 (0.0) = 0
-- prop> i32_sign_bit_f64 (1.0) = 0
--
-- All @`NaN`@s have a proper sign:
--
-- prop> 1 = i32_sign_bit_f64 (0/0)
-- prop> 0 = i32_sign_bit_f64 (castWord64ToDouble 0b0111111111111000000000000000000000000000000000000000000000000000)
{-# INLINABLE i32_sign_bit_f64 #-}
i32_sign_bit_f64 :: Double -> Int32
i32_sign_bit_f64 (D# x) = I32# (i32_sign_bit_f64# x)

{-# INLINABLE i32_sign_bit_f64# #-}
i32_sign_bit_f64# :: Double# -> Int32#
i32_sign_bit_f64# value = result
  where
    value_as_word64 :: Word64#
    value_as_word64 = stgDoubleToWord64 value

    sign_bit :: Word64#
    sign_bit
      = 0b1000000000000000000000000000000000000000000000000000000000000000#Word64

    sign_bit_of_value :: Word64#
    sign_bit_of_value = and64# sign_bit value_as_word64

    result
      = case sign_bit_of_value of
          0b1000000000000000000000000000000000000000000000000000000000000000#Word64
            -> 0b1#Int32
          0b0000000000000000000000000000000000000000000000000000000000000000#Word64
            -> 0b0#Int32
          _ -> 0b0#Int32 -- error "cannot happen"



{-# INLINABLE i32_sign_bit_f32 #-}
i32_sign_bit_f32 :: Float -> Int32
i32_sign_bit_f32 (F# x) = I32# (i32_sign_bit_f32# x)

{-# INLINABLE i32_sign_bit_f32# #-}
i32_sign_bit_f32# :: Float# -> Int32#
i32_sign_bit_f32# value = result
  where
    value_as_word32 :: Word32#
    value_as_word32 = stgFloatToWord32 value

    sign_bit :: Word32#
    sign_bit
      = 0b10000000000000000000000000000000#Word32

    sign_bit_of_value :: Word32#
    sign_bit_of_value = andWord32# sign_bit value_as_word32

    result
      = case sign_bit_of_value of
          0b10000000000000000000000000000000#Word32
            -> 0b1#Int32
          0b00000000000000000000000000000000#Word32
            -> 0b0#Int32
          _ -> 0b0#Int32 -- error "cannot happen"


-----------



{-# INLINABLE f64_successorIEEE# #-}
f64_successorIEEE#
  :: Double#
  -> Double#
f64_successorIEEE#
  value
  = result
  where
    result = resulting_double

    value_as_word64 :: Word64#
    value_as_word64 = stgDoubleToWord64 value

    sign_bit :: Word64#
    sign_bit
      = 0b1000000000000000000000000000000000000000000000000000000000000000#Word64

    exponent_bits :: Word64#
    exponent_bits
      = 0b0111111111110000000000000000000000000000000000000000000000000000#Word64

    _negativ_infinity :: Word64#
    _negativ_infinity
      = 0b1111111111110000000000000000000000000000000000000000000000000000#Word64

    _least_finit_value :: Word64#
    _least_finit_value
      = 0b1111111111101111111111111111111111111111111111111111111111111111#Word64
      -- -1.7976931348623157e308##

    _least_positiv_value :: Word64#
    _least_positiv_value
      = 0b0000000000000000000000000000000000000000000000000000000000000001#Word64
      -- 5.0e-324##

    _negative_zero = sign_bit

    exponent_bits_of_value :: Word64#
    exponent_bits_of_value = and64# exponent_bits value_as_word64

    sign_bit_of_value :: Word64#
    sign_bit_of_value = and64# sign_bit value_as_word64

    resulting_double
      = case exponent_bits_of_value of
          {- not real value or not negativ zero -}
          0b0111111111110000000000000000000000000000000000000000000000000000#Word64 {-exponent_bits-}
            -> case value_as_word64 of
                0b1111111111110000000000000000000000000000000000000000000000000000#Word64 {-negativ_infinity-}
                  -> -1.7976931348623157e308## {-least_finit_value-}
                _
                  -> value --not real values stay as they are (except for negativ_infinity)

          {- real value or negativ zero -}
          _
            -> case sign_bit_of_value of
                {-positive real value-}
                0b0000000000000000000000000000000000000000000000000000000000000000#Word64
                  -> stgWord64ToDouble (plusWord64# 1#Word64 value_as_word64)

                {-negative real value-}
                _
                  -> case value_as_word64 of
                      {-negative_zero-}
                      0b1000000000000000000000000000000000000000000000000000000000000000#Word64 {-negative_zero-}
                        -> 5.0e-324## {-least_positiv_value-}

                      {-proper negativ value-}
                      _
                        -> stgWord64ToDouble (subWord64# value_as_word64 1#Word64)


{-|
This function is supposed to be a pure haskell replacement for `Numeric.IEEE.succIEEE`. ([succIEEE](https://hackage.haskell.org/package/ieee754-0.8.0/docs/Numeric-IEEE.html#v:succIEEE))

Floating point numbers of the same sign have canonical ordered bitpatterns. This means that first a float that represents a real number can first be reinterpeted as an integer. Then one can add or subtract `1` inorder to get the two floating point neighbours. Special cases are infinit values and negative and positive zero. @`NaN`@s are returned unchanged.

prop> f64_successorIEEE (-1/0) = -1.7976931348623157e308
prop> f64_successorIEEE (-0.0) = castWord64ToDouble 1
prop> f64_successorIEEE (0.0) = castWord64ToDouble 1
prop> f64_successorIEEE (castWord64ToDouble 1) = castWord64ToDouble 2
prop> f64_successorIEEE (castWord64ToDouble 2) = castWord64ToDouble 3
prop> f64_successorIEEE (1.7976931348623157e308) = 1/0
-}
{-# INLINABLE f64_successorIEEE #-}
f64_successorIEEE
  :: Double
  -> Double
f64_successorIEEE
  (D# value#)
  = D# (f64_successorIEEE# value#)




{-# INLINABLE f64_predecessorIEEE# #-}
f64_predecessorIEEE#
  :: Double#
  -> Double#
f64_predecessorIEEE#
  value
  = symetric_result
  where
    symetric_result
      = negateDouble#
      $# f64_successorIEEE#
      $# negateDouble#
      $# value

    infixr 0 $#
    ($#) :: (Double# -> Double#) -> Double# -> Double#
    f $# x = f x


{-# INLINABLE f64_predecessorIEEE #-}
f64_predecessorIEEE
  :: Double
  -> Double
f64_predecessorIEEE
  (D# value#)
  = D# (f64_predecessorIEEE# value#)





------------




{-# INLINABLE f32_successorIEEE# #-}
f32_successorIEEE#
  :: Float#
  -> Float#
f32_successorIEEE#
  value
  = result
  where
    result = resulting_float

    value_as_word64 :: Word32# --TODO recent GHC returns Word32#
    value_as_word64 = stgFloatToWord32 value

    sign_bit :: Word32#
    sign_bit
      = 0b10000000000000000000000000000000#Word32

    exponent_bits :: Word32#
    exponent_bits
      = 0b01111111100000000000000000000000#Word32

    _negativ_infinity :: Word32#
    _negativ_infinity
      = 0b11111111100000000000000000000000#Word32

    _least_finit_value :: Word32#
    _least_finit_value
      = 0b11111111011111111111111111111111#Word32
      -- -3.4028235e38#

    _least_positiv_value :: Word32#
    _least_positiv_value
      = 0b00000000000000000000000000000001#Word32
      -- 1.0e-45#

    _negative_zero = sign_bit

    exponent_bits_of_value :: Word32#
    exponent_bits_of_value = andWord32# exponent_bits value_as_word64

    sign_bit_of_value :: Word32#
    sign_bit_of_value = andWord32# sign_bit value_as_word64

    resulting_float
      = case exponent_bits_of_value of
          {- not real value or not negativ zero -}
          0b01111111100000000000000000000000#Word32 {-exponent_bits-}
            -> case value_as_word64 of
                0b11111111100000000000000000000000#Word32 {-negativ_infinity-}
                  -> -3.4028235e38# {-least_finit_value-}
                _
                  -> value --not real values stay as they are (except for negativ_infinity)

          {- real value or negativ zero -}
          _
            -> case sign_bit_of_value of
                {-positive real value-}
                0b00000000000000000000000000000000#Word32
                  -> stgWord32ToFloat (plusWord32# 1#Word32 value_as_word64)

                {-negative real value-}
                _
                  -> case value_as_word64 of
                      {-negative_zero-}
                      0b10000000000000000000000000000000#Word32 {-negative_zero-}
                        -> 1.0e-45# {-least_positiv_value-}

                      {-proper negativ value-}
                      _
                        -> stgWord32ToFloat (minusWord32# value_as_word64 1#Word32)

    minusWord32# :: Word32# -> Word32# -> Word32#
    minusWord32# x y = wordToWord32# (minusWord# (word32ToWord# x) (word32ToWord# y))


{-# INLINABLE f32_successorIEEE #-}
f32_successorIEEE
  :: Float
  -> Float
f32_successorIEEE
  (F# value#)
  = F# (f32_successorIEEE# value#)



{-# INLINABLE f32_predecessorIEEE# #-}
f32_predecessorIEEE#
  :: Float#
  -> Float#
f32_predecessorIEEE#
  value
  = result
  where
    symetric_result
      = negateFloat#
      $# f32_successorIEEE#
      $# negateFloat#
      $# value

    infixr 0 $#
    ($#) :: (Float# -> Float#) -> Float# -> Float#
    f $# x = f x

    result
      = if isNegativeZero (F# symetric_result)
          then 0.0#
          else symetric_result

{-# INLINABLE f32_predecessorIEEE #-}
f32_predecessorIEEE
  :: Float
  -> Float
f32_predecessorIEEE
  (F# value#)
  = F# (f32_predecessorIEEE# value#)










-----------



-- |
-- prop> (castWord64ToDouble 1) == f32_demote_f64_ceil (castWord64ToDouble 1)
f32_demote_f64_ceil :: Double -> Float
f32_demote_f64_ceil = sanatizeNaNDemotion $ sanatizeNegtiveZeroDemotionPromotion $ convert RoundUp

f32_demote_f64_floor :: Double -> Float
f32_demote_f64_floor = sanatizeNaNDemotion $ sanatizeNegtiveZeroDemotionPromotion $ convert RoundDown

f32_demote_f64_truncate :: Double -> Float
f32_demote_f64_truncate = sanatizeNaNDemotion $ sanatizeNegtiveZeroDemotionPromotion $ convert Truncate



f64_promote_f32_ceil :: Float -> Double
f64_promote_f32_ceil = sanatizeNaNPromotion $ sanatizeNegtiveZeroDemotionPromotion $ convert RoundUp

f64_promote_f32_floor :: Float -> Double
f64_promote_f32_floor = sanatizeNaNPromotion $ sanatizeNegtiveZeroDemotionPromotion $ convert RoundDown

f64_promote_f32_truncate :: Float -> Double
f64_promote_f32_truncate = sanatizeNaNPromotion $ sanatizeNegtiveZeroDemotionPromotion $ convert Truncate




-- |
-- prop> 0.0 = f32_squareRoot_ceil 0.0
-- prop> 1.0 = f32_squareRoot_ceil 1.0
-- prop> (1/0) = f32_squareRoot_ceil (1/0)
-- prop> True = isNaN (f32_squareRoot_ceil (-1/0))
f32_squareRoot_ceil :: Float -> Float
f32_squareRoot_ceil = squareRoot RoundUp

f32_squareRoot_floor :: Float -> Float
f32_squareRoot_floor = squareRoot RoundDown

-- |
-- prop> f32_squareRoot_truncate = f32_squareRoot_floor
f32_squareRoot_truncate :: Float -> Float
f32_squareRoot_truncate = squareRoot Truncate

f64_squareRoot_ceil :: Double -> Double
f64_squareRoot_ceil = squareRoot RoundUp

f64_squareRoot_floor :: Double -> Double
f64_squareRoot_floor = squareRoot RoundDown

f64_squareRoot_truncate :: Double -> Double
f64_squareRoot_truncate = squareRoot Truncate

f32_add_ceil :: Float -> Float -> Float
f32_add_ceil = binary_operator_rounded RoundUp (+)

-- |
-- prop> (-0.0) = f32_add_floor 1 (-1)
-- prop> (-0.0) = f32_add_floor (-1) 1
f32_add_floor :: Float -> Float -> Float
f32_add_floor = sanitizeZeroForAddition $ binary_operator_rounded RoundDown (+)

f32_add_truncate :: Float -> Float -> Float
f32_add_truncate = binary_operator_rounded Truncate (+)

-- |
-- prop> 0.0 == f32_subtract_ceil 1 1
f32_subtract_ceil :: Float -> Float -> Float
f32_subtract_ceil = binary_operator_rounded RoundUp (-)

f32_subtract_floor :: Float -> Float -> Float
f32_subtract_floor = sanitizeZeroForSubtraction $ binary_operator_rounded RoundDown (-)

f32_subtract_truncate :: Float -> Float -> Float
f32_subtract_truncate = binary_operator_rounded Truncate (-)

f32_multiplicate_ceil :: Float -> Float -> Float
f32_multiplicate_ceil = binary_operator_rounded RoundUp (*)

f32_multiplicate_floor :: Float -> Float -> Float
f32_multiplicate_floor = binary_operator_rounded RoundDown (*)

f32_multiplicate_truncate :: Float -> Float -> Float
f32_multiplicate_truncate = binary_operator_rounded Truncate (*)

f32_divide_ceil :: Float -> Float -> Float
f32_divide_ceil = binary_operator_rounded RoundUp (/)

f32_divide_floor :: Float -> Float -> Float
f32_divide_floor = binary_operator_rounded RoundDown (/)

f32_divide_truncate :: Float -> Float -> Float
f32_divide_truncate = binary_operator_rounded Truncate (/)

-- |
-- prop> 0.0 = f64_add_ceil 1 (-1)
f64_add_ceil :: Double -> Double -> Double
f64_add_ceil = binary_operator_rounded RoundUp (+)

f64_add_floor :: Double -> Double -> Double
f64_add_floor = sanitizeZeroForAddition $ binary_operator_rounded RoundDown (+)

f64_add_truncate :: Double -> Double -> Double
f64_add_truncate = binary_operator_rounded Truncate (+)

f64_subtract_ceil :: Double -> Double -> Double
f64_subtract_ceil = binary_operator_rounded RoundUp (-)

f64_subtract_floor :: Double -> Double -> Double
f64_subtract_floor = sanitizeZeroForSubtraction $ binary_operator_rounded RoundDown (-)

f64_subtract_truncate :: Double -> Double -> Double
f64_subtract_truncate = binary_operator_rounded Truncate (-)

f64_multiplicate_ceil :: Double -> Double -> Double
f64_multiplicate_ceil = binary_operator_rounded RoundUp (*)

f64_multiplicate_floor :: Double -> Double -> Double
f64_multiplicate_floor = binary_operator_rounded RoundDown (*)

f64_multiplicate_truncate :: Double -> Double -> Double
f64_multiplicate_truncate = binary_operator_rounded Truncate (*)

f64_divide_ceil :: Double -> Double -> Double
f64_divide_ceil = binary_operator_rounded RoundUp (/)

f64_divide_floor :: Double -> Double -> Double
f64_divide_floor = binary_operator_rounded RoundDown (/)

f64_divide_truncate :: Double -> Double -> Double
f64_divide_truncate = binary_operator_rounded Truncate (/)

f32_convert_i32_unsigned_ceil :: Word32 -> Float
f32_convert_i32_unsigned_ceil = convert RoundUp

f32_convert_i32_signed_ceil :: Int32 -> Float
f32_convert_i32_signed_ceil = convert RoundUp

f32_convert_i32_unsigned_floor :: Word32 -> Float
f32_convert_i32_unsigned_floor = convert RoundDown

f32_convert_i32_signed_floor :: Int32 -> Float
f32_convert_i32_signed_floor = convert RoundDown

f32_convert_i32_unsigned_truncate :: Word32 -> Float
f32_convert_i32_unsigned_truncate = convert Truncate

f32_convert_i32_signed_truncate :: Int32 -> Float
f32_convert_i32_signed_truncate = convert Truncate

f32_convert_i64_unsigned_ceil :: Word64 -> Float
f32_convert_i64_unsigned_ceil = convert RoundUp

f32_convert_i64_signed_ceil :: Int64 -> Float
f32_convert_i64_signed_ceil = convert RoundUp

f32_convert_i64_unsigned_floor :: Word64 -> Float
f32_convert_i64_unsigned_floor = convert RoundDown

f32_convert_i64_signed_floor :: Int64 -> Float
f32_convert_i64_signed_floor = convert RoundDown

f32_convert_i64_unsigned_truncate :: Word64 -> Float
f32_convert_i64_unsigned_truncate = convert Truncate

f32_convert_i64_signed_truncate :: Int64 -> Float
f32_convert_i64_signed_truncate = convert Truncate

f64_convert_i32_unsigned_ceil :: Word32 -> Double
f64_convert_i32_unsigned_ceil = convert RoundUp

f64_convert_i32_signed_ceil :: Int32 -> Double
f64_convert_i32_signed_ceil = convert RoundUp

f64_convert_i32_unsigned_floor :: Word32 -> Double
f64_convert_i32_unsigned_floor = convert RoundDown

f64_convert_i32_signed_floor :: Int32 -> Double
f64_convert_i32_signed_floor = convert RoundDown

f64_convert_i32_unsigned_truncate :: Word32 -> Double
f64_convert_i32_unsigned_truncate = convert Truncate

f64_convert_i32_signed_truncate :: Int32 -> Double
f64_convert_i32_signed_truncate = convert Truncate

f64_convert_i64_unsigned_ceil :: Word64 -> Double
f64_convert_i64_unsigned_ceil = convert RoundUp

f64_convert_i64_signed_ceil :: Int64 -> Double
f64_convert_i64_signed_ceil = convert RoundUp

f64_convert_i64_unsigned_floor :: Word64 -> Double
f64_convert_i64_unsigned_floor = convert RoundDown

f64_convert_i64_signed_floor :: Int64 -> Double
f64_convert_i64_signed_floor = convert RoundDown

f64_convert_i64_unsigned_truncate :: Word64 -> Double
f64_convert_i64_unsigned_truncate = convert Truncate

f64_convert_i64_signed_truncate :: Int64 -> Double
f64_convert_i64_signed_truncate = convert Truncate





---




-- https://stackoverflow.com/questions/28949774/what-is-0-0-by-ieee-floating-point-standard
sanitizeZeroForAddition
  :: (Eq a1, Num a1, RealFloat a2)
  => (a2 -> a2 -> a1)
  -> (a2 -> a2 -> a1)
sanitizeZeroForAddition
  operator
  a
  b
  = if (0 == x) && (sign_bit a * sign_bit b == -1)
      then negate x
      else x
  where
    x = operator a b

    sign_bit y
      = if 0 == y
          then (boolToNumber $ isNegativeZero y)
          else signum y

    boolToNumber True = -1
    boolToNumber False = 1

sanitizeZeroForSubtraction
  :: (Eq a1, RealFloat a2, Num a1)
  => (a2 -> a2 -> a1)
  -> (a2 -> a2 -> a1)
sanitizeZeroForSubtraction
  operator
  a
  b
  = if (0 == x) && (sign_bit a == sign_bit b)
      then negate x
      else x
  where
    x = operator a b

    sign_bit y
      = if 0 == y
          then (boolToNumber $ isNegativeZero y)
          else signum y

    boolToNumber True = -1
    boolToNumber False = 1

sanatizeNegtiveZeroDemotionPromotion
  :: (RealFloat p, Fractional a)
  => (p -> a)
  -> (p -> a)
sanatizeNegtiveZeroDemotionPromotion
  conversion
  input
  = result
  where
    result
      = if isNegativeZero input
          then negate 0.0
          else conversion input

sanatizeNaNDemotion
  :: (Double -> Float)
  -> (Double -> Float)
sanatizeNaNDemotion
  conversion
  input
  = result
  where
    result
      = if isNaN input
          then double2Float input
          else conversion input

sanatizeNaNPromotion
  :: (Float -> Double)
  -> (Float -> Double)
sanatizeNaNPromotion
  conversion
  input
  = result
  where
    result
      = if isNaN input
          then float2Double input
          else conversion input




---------




data RationalWithSpecials
  = NegativInfinity
  | Exisiting Rational
  | PositiveInfinity
  deriving (Show, Eq)

getExisiting :: Rational -> RationalWithSpecials -> Rational
getExisiting _ (Exisiting x) = x
getExisiting defaultValue _ = defaultValue


-- | left: exact result, right: wannebe result
liftComparator
  :: Round
  -> (Rational -> Rational -> Bool)
  -> RationalWithSpecials
  -> RationalWithSpecials
  -> Bool
liftComparator _ sorted (Exisiting x) (Exisiting y) = x `sorted` y

liftComparator RoundDown _ PositiveInfinity PositiveInfinity = True

liftComparator RoundDown _ _ PositiveInfinity = False
liftComparator RoundDown _ _ NegativInfinity = True

liftComparator RoundUp _ PositiveInfinity PositiveInfinity = True
liftComparator RoundUp _ _ PositiveInfinity = True
liftComparator RoundUp _ _ NegativInfinity = False

liftComparator Truncate _ xx@(Exisiting x) PositiveInfinity = if 0 < x then liftComparator RoundDown undefined xx PositiveInfinity else undefined --error "off by sign"
liftComparator Truncate _ xx@(Exisiting x) NegativInfinity = if x < 0 then liftComparator RoundUp undefined xx NegativInfinity else undefined --error "off by sign"

liftComparator Truncate _ PositiveInfinity PositiveInfinity = True

liftComparator _roundDirection _sorted _x _y = undefined --error $ show ("liftComparator", roundDirection, x, y)




liftComparatorConvert
  :: Round
  -> (Rational -> Rational -> Bool)
  -> RationalWithSpecials
  -> RationalWithSpecials
  -> Bool
liftComparatorConvert _ sorted (Exisiting x) (Exisiting y) = x `sorted` y
liftComparatorConvert RoundDown _sorted PositiveInfinity (Exisiting _) = False
liftComparatorConvert RoundDown _ NegativInfinity (Exisiting _) = True

liftComparatorConvert RoundUp _sorted PositiveInfinity (Exisiting _) = True
liftComparatorConvert RoundUp _sorted NegativInfinity (Exisiting _) = False

liftComparatorConvert Truncate _ PositiveInfinity xx@(Exisiting x)
  = if 0 < x
      then liftComparatorConvert RoundDown undefined PositiveInfinity xx
      else undefined --off_by_sign
liftComparatorConvert Truncate _ NegativInfinity xx@(Exisiting x)
  = if x < 0
      then liftComparatorConvert RoundUp undefined NegativInfinity xx
      else undefined --off_by_sign

liftComparatorConvert _ _sorted PositiveInfinity PositiveInfinity = True
liftComparatorConvert _ _sorted NegativInfinity NegativInfinity = True

liftComparatorConvert _roundDirection _sorted _x _y = undefined --error $ show ("liftComparatorConvert", roundDirection, x, y)





liftUnary
  :: (Rational -> Rational)
  -> (RationalWithSpecials -> RationalWithSpecials)
liftUnary f (Exisiting x) = Exisiting $ f x
liftUnary _ PositiveInfinity = PositiveInfinity
liftUnary _ NegativInfinity = PositiveInfinity





class RationalWithSpecialsLike a where
  exactToRational :: {-HasCallStack =>-} a -> RationalWithSpecials


class
  (Show a, Num a, RealFloat a, RationalWithSpecialsLike a)
  => IEEE_RoundingFuckUp_Correctable a
  where
  exactFromRational :: {-HasCallStack =>-} RationalWithSpecials -> a
  nudgeDown :: a -> a
  nudgeUp :: a -> a
  nudgeToZero :: a -> a
  nudgeToZero j
    = if j < 0
        then nudgeUp j
        else if 0 < j
          then nudgeDown j
          else undefined -- error "what now?"
  nudgeAwayFromZero :: a -> a
  nudgeAwayFromZero j
    = if j < 0
        then nudgeDown j
        else if 0 < j
          then nudgeUp j
          else undefined -- error "what now?"


instance RationalWithSpecialsLike Word32 where
  exactToRational = Exisiting . toRational
instance RationalWithSpecialsLike Word64 where
  exactToRational = Exisiting . toRational
instance RationalWithSpecialsLike Int32 where
  exactToRational = Exisiting . toRational
instance RationalWithSpecialsLike Int64 where
  exactToRational = Exisiting . toRational

withFrozenCallStack :: a -> a
withFrozenCallStack = id

assertNote :: a -> b -> c -> c
assertNote _note _check = id

instance IEEE_RoundingFuckUp_Correctable Double where
  exactFromRational = withFrozenCallStack hopefullyExactFromRational
  nudgeDown = f64_predecessorIEEE
  nudgeUp = f64_successorIEEE

instance RationalWithSpecialsLike Double where
  exactToRational = withFrozenCallStack hopeFullyExactToRational


instance IEEE_RoundingFuckUp_Correctable Float where
  exactFromRational = withFrozenCallStack hopefullyExactFromRational
  nudgeDown = f32_predecessorIEEE
  nudgeUp = f32_successorIEEE


instance RationalWithSpecialsLike Float where
  exactToRational = withFrozenCallStack hopeFullyExactToRational

hopeFullyExactToRational
  :: ({-HasCallStack,-} RealFloat a, Show a)
  => a
  -> RationalWithSpecials
hopeFullyExactToRational
  value
  = id
  $ withFrozenCallStack
  $ assertNote note check
  $ result
  where
    result
      = if isInfinite value
          then case signum value of
                  -1 -> NegativInfinity
                  1 -> PositiveInfinity
                  _ -> undefined -- error "infinit has sign 0"
          else if not $ isNaN value
            then Exisiting $ toRational value
            else undefined --error $ "cannot convert to rational: " ++ show value

    note = "this double cannot be represented as Rational: " ++ show value

    check
      = (not $ isNaN $ value)
      && (not $ isNegativeZero $ value)


hopefullyExactFromRational
  :: ({-HasCallStack,-} RealFloat a)
  => RationalWithSpecials
  -> a
hopefullyExactFromRational (Exisiting rational) = fromRational rational
hopefullyExactFromRational (PositiveInfinity) = 1/0
hopefullyExactFromRational (NegativInfinity) = -1/0




------------





data Round = RoundDown | RoundUp | Truncate
  deriving Show


roundingOrder_binaryOperator
  :: Round
  -> (Rational -> Rational -> Bool)
roundingOrder_binaryOperator RoundDown = (>=)
roundingOrder_binaryOperator RoundUp = (<=)
roundingOrder_binaryOperator Truncate = \exact_result allegedly_rounded_result
  -> if exact_result < 0 && allegedly_rounded_result < 0
        then (roundingOrder_binaryOperator RoundUp) exact_result allegedly_rounded_result
        else if 0 < exact_result && 0 < allegedly_rounded_result
                then roundingOrder_binaryOperator RoundDown exact_result allegedly_rounded_result
                else if 0 == exact_result && 0 == allegedly_rounded_result
                  then True
                  else if 0 == allegedly_rounded_result
                    then True -- zero is allways rounded ok
                    else undefined -- error $ "roundingOrder_binaryOperator: unconsiderd case: " ++ show (exact_result, allegedly_rounded_result)

    -- exact_result `sorted` rounded_result
    -- -2.0 `sorted` -1.0
    -- 1.0 `sorted` 2.0

roundingOrder_UnaryOperator
  :: Round
  -> (Rational -> Rational -> Bool)
roundingOrder_UnaryOperator RoundDown = (<=)
roundingOrder_UnaryOperator RoundUp = (>=)
roundingOrder_UnaryOperator Truncate = \allegedly_rounded_result exact_result
  -> if allegedly_rounded_result < 0 && exact_result < 0
        then (roundingOrder_UnaryOperator RoundUp) allegedly_rounded_result exact_result
        else if 0 < allegedly_rounded_result && 0 < exact_result
                then roundingOrder_UnaryOperator RoundDown allegedly_rounded_result exact_result
                else if 0 == allegedly_rounded_result && 0 == exact_result
                  then True
                  else if 0 == allegedly_rounded_result
                    then True -- zero is allways rounded ok
                    else undefined -- error $ "roundingOrder_binaryOperator: unconsiderd case: " ++ show (exact_result, allegedly_rounded_result)

chooseNudging
  :: IEEE_RoundingFuckUp_Correctable a
  => Round
  -> a
  -> a
chooseNudging RoundDown = nudgeDown
chooseNudging RoundUp = nudgeUp
chooseNudging Truncate = nudgeToZero

chooseTheTheOtherNudging
  :: IEEE_RoundingFuckUp_Correctable a
  => Round
  -> a
  -> a
chooseTheTheOtherNudging RoundDown = nudgeUp
chooseTheTheOtherNudging RoundUp = nudgeDown
chooseTheTheOtherNudging Truncate = nudgeAwayFromZero




------------




binary_operator_rounded
  :: Round
  -> (forall a. (Num a, Fractional a) => a -> a -> a)
  -> ((IEEE_RoundingFuckUp_Correctable b) => b -> b -> b)
binary_operator_rounded
  upOrDown
  operator
  left
  right
  = id
  $ result
  where
    sorted
      = liftComparator upOrDown
      $ roundingOrder_binaryOperator upOrDown

    nudge = chooseNudging upOrDown

    the_other_nudge = chooseTheTheOtherNudging upOrDown

    badArguments = undefined --error $ show ("case not supported", left, right)
    left_rational = getExisiting badArguments $ exactToRational left
    right_rational = getExisiting badArguments $ exactToRational right

    exact_result :: Rational
    exact_result = left_rational `operator` right_rational

    somehow_rounded_result = left `operator` right

    somehow_rounded_result_as_rational
      = exactToRational
      $ somehow_rounded_result

    was_already_rounded_correctly
      = (Exisiting exact_result) `sorted` somehow_rounded_result_as_rational

    correctly_rounded_result
      = if not (isNaN somehow_rounded_result || 0 == right)
          then if was_already_rounded_correctly
            then somehow_rounded_result
            else nudge somehow_rounded_result
          else somehow_rounded_result


    note
      = "new nudging did not work: "
      ++ show
      ( upOrDown
      , left
      , right
      )

    check
      = is_special_case
      ||
      (True
        && (Exisiting exact_result)
            `sorted`
              (exactToRational correctly_rounded_result)
        && (exactToRational $ the_other_nudge correctly_rounded_result)
            `strict_sorted`
              (Exisiting exact_result)
      )

    strict_sorted x y = (x /= y) && (x `sorted` y)


    is_special_case = False
      || isNaN left
      || isNaN right
      || isNegativeZero left
      || isNegativeZero right
      || isInfinite left
      || isInfinite right

    result
      = if not (isNaN left || isNaN right)
          then normal_case
          else somehow_rounded_result

    normal_case
      = if not(isInfinite left || isInfinite right)
          then propernumbercase
          else somehow_rounded_result

    propernumbercase = assertNote note check correctly_rounded_result






------------




squareRoot
  :: (Floating a, IEEE_RoundingFuckUp_Correctable a)
  => Round
  -> a
  -> a
squareRoot
  upOrDown
  preImage
  = result
  where
    sorted
      = liftComparator upOrDown
      $ roundingOrder_UnaryOperator upOrDown
      -- TODO refactor into one function

    nudge = chooseNudging upOrDown

    the_other_nudge = chooseTheTheOtherNudging upOrDown

    result = normal_case

    somehowRoundedResult = sqrt preImage

    preImage_Rational = exactToRational preImage

    somehowRoundedResult_preImage :: RationalWithSpecials
    somehowRoundedResult_preImage
      = id
      $ liftUnary (\x -> x*x)
      $ exactToRational
      $ somehowRoundedResult

    -- this works because squareroot is monotonic
    wasRoundedCorrectly
      = somehowRoundedResult_preImage
      `sorted` preImage_Rational

    correctly_rounded_result
      = if (not $ isNaN somehowRoundedResult) || (isNegativeZero preImage)
          then
            if wasRoundedCorrectly
              then somehowRoundedResult
              else nudge somehowRoundedResult
          else
            somehowRoundedResult

    normal_case = assertNote note check correctly_rounded_result

    note = "something went wrong"
    check
      = True
      && we_rounded_correctly
      && we_rounded_tightly

    we_rounded_correctly = ((liftUnary (\x->x*x) $ exactToRational correctly_rounded_result) `sorted` (exactToRational preImage))
    we_rounded_tightly = (exactToRational preImage) `strict_sorted` (liftUnary (\x->x*x) $ exactToRational (the_other_nudge correctly_rounded_result))
      where
        strict_sorted x y = (x /= y) && (x `sorted` y)





convert
  ::
    ( Show a
    , Real a
    , RationalWithSpecialsLike a
    , IEEE_RoundingFuckUp_Correctable b
    )
  => Round
  -> a
  -> b
convert
  upOrDown
  integral
  = id
  $ result
  where
    result = normal_case

    sorted
      = liftComparatorConvert upOrDown
      $ roundingOrder_UnaryOperator upOrDown

    nudge = chooseNudging upOrDown

    the_other_nudge = chooseTheTheOtherNudging upOrDown

    argument_Rational = exactToRational $ integral

    somehowRoundedResult = exactFromRational $ argument_Rational

    somehow_rounded_result_Rational
      = exactToRational somehowRoundedResult

    wasRoundedCorrectly
      = somehow_rounded_result_Rational
      `sorted` argument_Rational

    correctly_rounded_result
      = if wasRoundedCorrectly
          then somehowRoundedResult
          else nudge somehowRoundedResult

    normal_case = assertNote note check correctly_rounded_result

    note = "something went wrong"
    check
      = True
      && we_rounded_correctly
      && we_rounded_tightly

    we_rounded_correctly = (Exisiting $ toRational correctly_rounded_result) `sorted` (Exisiting $ toRational integral)
    we_rounded_tightly = (Exisiting $ toRational integral) `strict_sorted` (exactToRational $ the_other_nudge correctly_rounded_result)
      where
        strict_sorted = liftComparator upOrDown $ \x y -> (x /= y) && (Exisiting x `sorted` Exisiting y)