{- | 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)