packages feed

moonlight-triangulation-1.0.0.0: src-core/Moonlight/Triangulation/Scalar.hs

-- | The binary64 coordinate kernel and exact predicate boundary.
module Moonlight.Triangulation.Scalar
  ( scalarName
  , scalarByteSize
  , scalarBinaryFormat
  , scalarEpsilon
  , scalarUnitRoundoff
  , scalarCcwErrorBound
  , scalarInCircleErrorBound
  , orient2dCoordinates
  , inCircleCoordinates
  , BinaryFormat
  , formatRadix
  , formatMantissaDigits
  , formatExponentRange
  , minimumAllowedCoordinate
  , maximumAllowedCoordinate
  , canonicalScalarZero
  ) where

import Moonlight.Triangulation.Internal.Dyadic (exactInCircleDet, exactOrientSignDouble)

-- | The coordinate component of canonical point identity. IEEE signed zeros
-- compare equal but hash differently by bits; every coordinate-keyed owner
-- therefore normalizes them before storage or hashing.
canonicalScalarZero :: Double -> Double
canonicalScalarZero value
  | value == 0 = 0
  | otherwise = value
{-# INLINE canonicalScalarZero #-}

-- | Machine format of the coordinate scalar.
data BinaryFormat = BinaryFormat
  { formatRadix :: !Integer
    -- ^ Numeric base of the significand.
  , formatMantissaDigits :: !Int
    -- ^ Number of base-'formatRadix' digits in the significand.
  , formatExponentRange :: !(Int, Int)
    -- ^ Inclusive minimum and exclusive maximum exponent bounds.
  }
  deriving stock (Eq, Show)

-- | Stable name of the coordinate scalar.
scalarName :: String
scalarName = "binary64"

-- | Bytes occupied by one coordinate component.
scalarByteSize :: Int
scalarByteSize = 8

-- | Runtime-confirmed binary format of 'Double'.
scalarBinaryFormat :: BinaryFormat
scalarBinaryFormat =
  BinaryFormat
    { formatRadix = floatRadix (0 :: Double)
    , formatMantissaDigits = floatDigits (0 :: Double)
    , formatExponentRange = floatRange (0 :: Double)
    }

-- | Difference between one and the next representable value above one.
scalarEpsilon :: Double
scalarEpsilon = 2.220446049250313e-16

-- | Maximum relative rounding error of one binary64 operation.
scalarUnitRoundoff :: Double
scalarUnitRoundoff = 1.1102230246251565e-16

-- | Error coefficient for the filtered orientation predicate.
scalarCcwErrorBound :: Double
scalarCcwErrorBound = 3.3306690738754716e-16

-- | Error coefficient for the filtered in-circle predicate.
scalarInCircleErrorBound :: Double
scalarInCircleErrorBound = 1.1102230246251577e-15

-- | Exact orientation ordering of three binary64 coordinate pairs.
orient2dCoordinates
  :: Double -> Double -> Double -> Double -> Double -> Double
  -> Ordering
orient2dCoordinates = filteredOrient2dDouble

-- | Exact in-circle ordering of four binary64 coordinate pairs.
inCircleCoordinates
  :: Double -> Double -> Double -> Double
  -> Double -> Double -> Double -> Double
  -> Ordering
inCircleCoordinates = filteredInCircle scalarInCircleErrorBound

-- The binary64 kernel pairs the approximation test with the
-- fixed-precision exact sign, which answers the dyadic determinant's sign in
-- machine words rather than allocated Integers whenever the exponent spread
-- allows, and defers to the dyadic determinant when it does not.
filteredOrient2dDouble
  :: Double -> Double -> Double -> Double -> Double -> Double -> Ordering
filteredOrient2dDouble ax ay bx by cx cy
  | abs determinant > errorBound * determinantSum = compare determinant 0
  | otherwise = exactOrientSignDouble ax ay bx by cx cy
 where
  errorBound = 3.3306690738754716e-16
  !left = (ax - cx) * (by - cy)
  !right = (ay - cy) * (bx - cx)
  !determinant = left - right
  !determinantSum = abs left + abs right
{-# INLINE filteredOrient2dDouble #-}

filteredInCircle
  :: Double
  -> Double -> Double -> Double -> Double
  -> Double -> Double -> Double -> Double
  -> Ordering
filteredInCircle errorBound ax ay bx by cx cy dx dy
  | abs determinant > errorBound * permanent = compare determinant 0
  | otherwise = compare (exactInCircleDet ax ay bx by cx cy dx dy) 0
 where
  !adx = ax - dx
  !ady = ay - dy
  !bdx = bx - dx
  !bdy = by - dy
  !cdx = cx - dx
  !cdy = cy - dy
  !abdet = adx * bdy - bdx * ady
  !bcdet = bdx * cdy - cdx * bdy
  !cadet = cdx * ady - adx * cdy
  !alift = adx * adx + ady * ady
  !blift = bdx * bdx + bdy * bdy
  !clift = cdx * cdx + cdy * cdy
  !determinant = alift * bcdet + blift * cadet + clift * abdet
  !permanent =
    (abs (bdx * cdy) + abs (cdx * bdy)) * alift
      + (abs (cdx * ady) + abs (adx * cdy)) * blift
      + (abs (adx * bdy) + abs (bdx * ady)) * clift
{-# INLINE filteredInCircle #-}

-- | The smallest coordinate magnitude the exact predicates accept.
minimumAllowedCoordinate :: Double
minimumAllowedCoordinate = 1.793662034335766e-43

-- | The largest coordinate magnitude the exact predicates accept.
maximumAllowedCoordinate :: Double
maximumAllowedCoordinate = 3.2138760885179806e60