{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Numeric.Rounded.Hardware.Internal.Show where
import Numeric.Rounded.Hardware.Internal.Rounding
import Data.Char (intToDigit)
import Data.Bifunctor (first)
import Data.Bits
import Math.NumberTheory.Logarithms
countTrailingZerosInteger :: Integer -> Int
countTrailingZerosInteger x
| x == 0 = error "countTrailingZerosInteger: zero"
| otherwise = integerLog2 (x `xor` (x - 1))
binaryFloatToDecimalDigitsRn :: forall a. RealFloat a
=> RoundingMode
-> Int
-> a
-> ([Int], Int)
binaryFloatToDecimalDigitsRn _rm _prec 0 = ([], 0)
binaryFloatToDecimalDigitsRn _rm _prec x | floatRadix x /= 2 = error "radix must be 2"
binaryFloatToDecimalDigitsRn rm prec x =
let m :: Integer
n, d, e0 :: Int
(m,n) = decodeFloat x
d = floatDigits x
e0 = floor (fromIntegral (d - 1 + n) * logBase 10 2 :: a) - prec
s, t :: Integer
(s,t) | n < 0, 0 <= e0 = (m, 2^(-n) * 10^e0)
| 0 <= e0 = (m * 2^n, 10^e0)
| n < 0 = (m * 10^(-e0), 2^(-n))
| otherwise = (m * 2^n * 10^(-e0), 1)
q, r :: Integer
(q,r) = s `quotRem` t
q', r', t' :: Integer
e' :: Int
(q',r',t',e') | 10^(prec+1) <= q = case q `quotRem` 10 of
(q'',r'') -> (q'', r''*t+r, 10*t, e0+1)
| otherwise = (q,r,t,e0)
in if r' == 0
then
loop0 e' q'
else
case rm of
TowardNegInf -> loop0 e' q'
TowardZero -> loop0 e' q'
TowardInf -> loop0 e' (q' + 1)
ToNearest -> case compare (2 * r') t' of
LT -> loop0 e' q'
EQ | even q' -> loop0 e' q'
| otherwise -> loop0 e' (q' + 1)
GT -> loop0 e' (q' + 1)
where
loop0 :: Int -> Integer -> ([Int], Int)
loop0 !_ 0 = ([], 0)
loop0 !e a = case a `quotRem` 10 of
(q,0) -> loop0 (e+1) q
(q,r) -> loop (e+1) [fromInteger r] q
loop :: Int -> [Int] -> Integer -> ([Int], Int)
loop !e acc 0 = (acc, e)
loop !e acc a = case a `quotRem` 10 of
(q,r) -> loop (e+1) (fromInteger r : acc) q
{-# SPECIALIZE binaryFloatToDecimalDigitsRn :: RoundingMode -> Int -> Double -> ([Int], Int) #-}
binaryFloatToFixedDecimalDigitsRn :: forall a. RealFloat a
=> RoundingMode
-> Int
-> a
-> [Int]
binaryFloatToFixedDecimalDigitsRn _rm _prec x | floatRadix x /= 2 = error "radix must be 2"
binaryFloatToFixedDecimalDigitsRn rm prec x =
let m, s, t, q, r :: Integer
e :: Int
(m,e) = decodeFloat x
(s,t) | prec >= 0, e + prec >= 0 = (m * 2^(e+prec) * 5^prec, 1)
| prec >= 0 = (m * 5^prec, 2^(-e-prec))
| e + prec >= 0 = (m * 2^(e+prec), 5^(-prec))
| otherwise = (m, 2^(-e-prec) * 5^(-prec))
(q,r) = s `quotRem` t
in if r == 0
then
loop [] q
else
case rm of
TowardNegInf -> loop [] q
TowardZero -> loop [] q
TowardInf -> loop [] (q + 1)
ToNearest -> case compare (2 * r) t of
LT -> loop [] q
EQ | even q -> loop [] q
| otherwise -> loop [] (q + 1)
GT -> loop [] (q + 1)
where
loop :: [Int] -> Integer -> [Int]
loop acc 0 = acc
loop acc a = case a `quotRem` 10 of
(q,r) -> loop (fromInteger r : acc) q
{-# SPECIALIZE binaryFloatToFixedDecimalDigitsRn :: RoundingMode -> Int -> Double -> [Int] #-}
binaryFloatToDecimalDigits :: RealFloat a
=> a
-> ([Int], Int)
binaryFloatToDecimalDigits 0 = ([], 0)
binaryFloatToDecimalDigits x | floatRadix x /= 2 = error "radix must be 2"
binaryFloatToDecimalDigits x =
let m, m', m'' :: Integer
n, z, n', e :: Int
(m,n) = decodeFloat x
z = countTrailingZerosInteger m
(m',n') = (m `shiftR` z, n + z)
(m'',e) | n' < 0 = (m' * 5^(-n'), n')
| otherwise = (m' * 2^n', 0)
in loop0 e m''
where
loop0 :: Int -> Integer -> ([Int], Int)
loop0 !_ 0 = ([0], 0)
loop0 !e a = case a `quotRem` 10 of
(q,0) -> loop0 (e+1) q
(q,r) -> loop (e+1) [fromInteger r] q
loop :: Int -> [Int] -> Integer -> ([Int], Int)
loop !e acc 0 = (acc, e)
loop !e acc n = case n `quotRem` 10 of
(q,r) -> loop (e+1) (fromInteger r : acc) q
{-# SPECIALIZE binaryFloatToDecimalDigits :: Double -> ([Int], Int) #-}
showEFloatRn :: RealFloat a => RoundingMode -> Maybe Int -> a -> ShowS
showEFloatRn r mprec x
| isNaN x = showString "NaN"
| x < 0 || isNegativeZero x = showChar '-' . showEFloatRn (oppositeRoundingMode r) mprec (-x)
| isInfinite x = showString "Infinity"
| otherwise = let (xs,e) = case mprec of
Nothing -> binaryFloatToDecimalDigits x
Just prec -> let !prec' = max prec 0
in first (padRight0 (prec' + 1)) $ binaryFloatToDecimalDigitsRn r prec' x
e' | all (== 0) xs = 0
| otherwise = e - 1
in case xs of
[] -> showString "0.0e0"
[0] -> showString "0e0"
[d] -> case mprec of
Nothing -> showString $ intToDigit d : '.' : '0' : 'e' : show e'
_ -> showString $ intToDigit d : 'e' : show e'
(d:ds) -> showString $ (intToDigit d : '.' : map intToDigit ds) ++ ('e' : show e')
where
padRight0 :: Int -> [Int] -> [Int]
padRight0 0 ys = ys
padRight0 !n [] = replicate n 0
padRight0 !n (y:ys) = y : padRight0 (n - 1) ys
{-# SPECIALIZE showEFloatRn :: RoundingMode -> Maybe Int -> Double -> ShowS #-}
showFFloatRn :: RealFloat a => RoundingMode -> Maybe Int -> a -> ShowS
showFFloatRn r mprec x
| isNaN x = showString "NaN"
| x < 0 || isNegativeZero x = showChar '-' . showFFloatRn (oppositeRoundingMode r) mprec (-x)
| isInfinite x = showString "Infinity"
| otherwise = case mprec of
Nothing -> let (xs,e) = binaryFloatToDecimalDigits x
l = length xs
in if e >= l
then if null xs
then showString "0.0"
else showString (map intToDigit xs ++ replicate (e - l) '0' ++ ".0")
else if e > 0
then if l == e
then showString (map intToDigit xs ++ ".0")
else let (ys,zs) = splitAt (l - e) xs
ys' | null ys = [0]
| otherwise = ys
in showString (map intToDigit ys' ++ "." ++ map intToDigit zs)
else
showString ("0." ++ replicate (-e) '0' ++ map intToDigit xs)
Just prec -> let prec' = max prec 0
xs = binaryFloatToFixedDecimalDigitsRn r prec' x
l = length xs
in if prec' == 0
then if null xs
then showString "0"
else showString $ map intToDigit xs
else if l <= prec'
then showString $ "0." ++ replicate (prec' - l) '0' ++ map intToDigit xs
else let (ys,zs) = splitAt (l - prec') xs
ys' | null ys = [0]
| otherwise = ys
in showString $ map intToDigit ys' ++ "." ++ map intToDigit zs
{-# SPECIALIZE showFFloatRn :: RoundingMode -> Maybe Int -> Double -> ShowS #-}
showGFloatRn :: RealFloat a => RoundingMode -> Maybe Int -> a -> ShowS
showGFloatRn r mprec x | x == 0 || (0.1 <= abs x && abs x < 1e7) = showFFloatRn r mprec x
| otherwise = showEFloatRn r mprec x
{-# SPECIALIZE showGFloatRn :: RoundingMode -> Maybe Int -> Double -> ShowS #-}