matrix-as-xyz-0.1.1.0: src/Data/Ratio/ParseFloat.hs
{- |
Module : Data.Ratio.ParseFloat
Copyright : (c) Jun Narumi 2018
License : BSD3
Maintainer : narumij@gmail.com
Stability : experimental
Portability : ?
Floating point parser
Temporary solution to the problem below
> ghci> realToFrac (read "1.1" :: Double) :: Rational
> 2476979795053773 % 2251799813685248
-}
module Data.Ratio.ParseFloat (
readFloatingPoint,
floating,
) where
import Data.Ratio
import Text.ParserCombinators.Parsec
-- このような一見、車輪の再発明に思えるコードをわざわざ書いたのは
-- ghci> realToFrac (read "1.1" :: Double) :: Rational
-- 2476979795053773 % 2251799813685248
-- という問題に対処するため。
-- これ以外に良い方法、良い書き方が分かれば、削除します。
-- | Obtain fractions from floating point representation string
--
-- >>> readFloatingPoint "1.1"
-- 11 % 10
-- >>> readFloatingPoint "0.5"
-- 1 % 2
-- >>> readFloatingPoint ".5"
-- 1 % 2
-- >>> readFloatingPoint "10."
-- 10 % 1
-- >>> readFloatingPoint "10"
-- 10 % 1
-- >>> readFloatingPoint "10.2"
-- 51 % 5
-- >>> readFloatingPoint "1e-1"
-- 1 % 10
-- >>> readFloatingPoint "-0.5e-1"
-- (-1) % 20
-- >>> readFloatingPoint "5e2"
-- 500 % 1
-- >>> readFloatingPoint "5e+2"
-- 500 % 1
readFloatingPoint :: Integral a => String -> Ratio a
readFloatingPoint s = case parse floating s s of
Left e -> error $ show e
Right r -> r
zero :: CharParser () String
zero = do
char '0'
return "0"
num :: CharParser () String
num = do
x <- oneOf "123456789"
xs <- many digit
return $ x : xs
int :: CharParser () String
int = zero <|> num
sign :: CharParser () Char
sign = oneOf "-+"
decimal :: CharParser () String
decimal = do
char '.'
many digit
exponent' :: Integral a => CharParser () a
exponent' = do
oneOf "eE"
s <- optionMaybe sign
e <- int
return $ signPart s $ read' e
-- | Parser section
floating :: Integral a => CharParser () (Ratio a)
floating = do
s <- optionMaybe sign
i <- option "" int
f <- option "" decimal
e <- optionMaybe exponent'
return $ signPart s . expPart e $ intPart i + decimalPart f
signPart :: Num a => Maybe Char -> a -> a
signPart (Just '-') = negate
signPart _ = id
intPart :: Integral a => String -> Ratio a
intPart "" = 0
intPart i = read' i % 1
decimalPart :: Integral a => String -> Ratio a
decimalPart "" = 0
decimalPart f | read f == 0 = 0
| otherwise = read' f % (10 ^ length f)
-- 整数型のread instanceを隠蔽している
read' :: Integral a => String -> a
read' s = fromIntegral (read s :: Integer)
expPart :: Integral a => Maybe Integer -> Ratio a -> Ratio a
expPart Nothing = id
expPart (Just s) | s == 0 = id
| s < 0 = flip (/) (10 ^ abs s)
| otherwise = flip (*) (10 ^ s)