module NumericUtils
( module NumericUtils
, intToDigit
, FFFormat(..)
) where
import Data.Char
import GHC.Base
import GHC.Float (FFFormat(..), roundTo)
import Numeric (floatToDigits)
import Text.Printf.TH.Printer
intToDigitUpper :: Int -> Char
intToDigitUpper (I# i)
| isTrue# (i >=# 0#) && isTrue# (i <=# 9#) = unsafeChr (ord '0' + I# i)
| isTrue# (i >=# 10#) && isTrue# (i <=# 15#) = unsafeChr (ord 'A' + I# i 10)
| otherwise = error ("Char.intToDigit: not a digit " ++ show (I# i))
showIntAtBase :: (Integral i, Monoid b, Printer b) => i -> (Int -> Char) -> i -> b
showIntAtBase base toB n0 = showIt (quotRem n0 base) mempty
where
showIt (n, d) r =
case n of
0 -> r'
_ -> showIt (quotRem n base) r'
where
r' = cons (toB $ fromIntegral d) r
formatRealFloatAlt :: RealFloat a => FFFormat -> Maybe Int -> Bool -> Bool -> a -> String
formatRealFloatAlt fmt decs alt upper x
| isNaN x = "NaN"
| isInfinite x =
if x < 0
then "-Infinity"
else "Infinity"
| x < 0 || isNegativeZero x = '-' : doFmt fmt (floatToDigits (toInteger base) (x))
| otherwise = doFmt fmt (floatToDigits (toInteger base) x)
where
eChar
| upper = 'E'
| otherwise = 'e'
base = 10
doFmt format (is, e) =
let ds = map intToDigit is
in case format of
FFGeneric ->
let alt1 = doFmt FFExponent (is, e)
alt2 = doFmt FFFixed (is, e)
in if length alt2 > length alt1
then alt1
else alt2
FFExponent ->
case decs of
Nothing ->
let show_e' = show (e 1)
in case ds of
"0" -> "0.0" ++ (eChar : "0")
[d] -> d : ".0" ++ [eChar] ++ show_e'
(d:ds') -> d : '.' : ds' ++ [eChar] ++ show_e'
[] -> error "formatRealFloat/doFmt/FFExponent: []"
Just dec ->
let dec' = max dec 0
in case is of
[0] ->
(if dec' == 0
then "0"
else "0.") ++
take dec' (repeat '0') ++ (eChar : "0")
_ ->
let (ei, is') = roundTo base (dec' + 1) is
(d:ds') =
map
intToDigit
(if ei > 0
then init is'
else is')
in (if null ds' && not alt
then [d]
else d : ".") ++
ds' ++ eChar : show (e 1 + ei)
FFFixed ->
let mk0 ls =
case ls of
"" -> "0"
_ -> ls
in case decs of
Nothing
| e <= 0 -> "0." ++ replicate (e) '0' ++ ds
| otherwise ->
let f 0 s rs = mk0 (reverse s) ++ '.' : mk0 rs
f n s "" = f (n 1) ('0' : s) ""
f n s (r:rs) = f (n 1) (r : s) rs
in f e "" ds
Just dec ->
let dec' = max dec 0
in if e >= 0
then let (ei, is') = roundTo base (dec' + e) is
(ls, rs) =
splitAt (e + ei) (map intToDigit is')
in mk0 ls ++
(if null rs && not alt
then ""
else '.' : rs)
else let (ei, is') =
roundTo
base
dec'
(replicate (e) 0 ++ is)
d:ds' =
map
intToDigit
(if ei > 0
then is'
else 0 : is')
in d :
(if null ds' && not alt
then ""
else '.' : ds')
formatFloatHex :: RealFloat a => Maybe Int -> Bool -> Bool -> a -> String
formatFloatHex decs alt upper x = doFmt (floatToDigits 2 x)
where
pChar
| upper = 'P'
| otherwise = 'p'
round' =
case decs of
Just d ->
\x ->
let (a, b) = roundTo 16 d x
in ( intToDigit (a + 1)
, if a > 0
then tail b
else b)
Nothing -> \x -> ('1', x)
doFmt ([0], 0) = "0" ++ [pChar] ++ "+0"
doFmt ((1:bits), exp) =
first :
(if null digs && not alt
then ""
else ".") ++
map
(if upper
then intToDigitUpper
else intToDigit)
digs ++
[pChar] ++
(if exp >= 1
then "+"
else "") ++
show (exp 1)
where
(first, digs) = round' $ go bits
go (a:b:c:d:xs) = foldl (\a b -> 2 * a + b) 0 [a, b, c, d] : go xs
go [] = []
go ys = go (take 4 $ ys ++ repeat 0)
doFmt _ = error "nonsense"