Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More accommodating read for scientific notation in Haskell

Haskell's read is a bit too strict about floating point numbers:

$ ghci
GHCi, version 8.2.2: http://www.haskell.org/ghc/  :? for help
Prelude> read "-1E34" :: Double
-1.0e34
Prelude> read "-1.E34" :: Double
*** Exception: Prelude.read: no parse
Prelude>

Is there a version of read that accepts the second form? It is quite common in physical sciences. For example, Fortran reads and writes such forms.

Another example that Haskell doesn't support is ".1" for "0.1". This one is even more common. I just don't want to convert the input ascii file. . . .

like image 944
Ryo Avatar asked Apr 28 '18 16:04

Ryo


2 Answers

Here's a custom parser that does this, using megaparsec.

import Text.Megaparsec
import Text.Megaparsec.Char

realLiteral :: (MonadParsec e s m, Token s ~ Char) => m Double
realLiteral = mkFloat <$> sign <*> intgPart <*> fracPart <*> exponent
 where mkFloat sgn itg frc expn
           = fromIntegral sgn * (fromIntegral itg + frc) * 10^^expn
       sign = (-1) <$ char '-'
           <|> 1   <$ char '+'
           <|> pure 1
       intgPart = read . ('0':) <$> many digitChar
       fracPart = char '.' *> (toFrc<$>many digitChar)
               <|> pure 0
        where toFrc "" = 0
              toFrc digits = read digits / 10^length digits
       exponent = oneOf "eEdD" *> ((*) <$> sign <*> (read<$>some digitChar))
               <|> pure 0
[1 of 1] Compiling Main             ( wtmpf-file5764.hs, interpreted )
Ok, 1 module loaded.
*Main> parseMaybe realLiteral "1"
Just 1.0
*Main> parseMaybe realLiteral "-3"
Just (-3.0)
*Main> parseMaybe realLiteral "-9e+2"
Just (-900.0)
*Main> parseMaybe realLiteral ".3e+9"
Just 3.0e8
*Main> parseMaybe realLiteral "-1.E34"
Just (-1.0000000000000001e34)
*Main> parseMaybe realLiteral "-1.673986e-40"
Just (-1.6739859999999999e-40)
*Main> parseMaybe realLiteral "-3.E+16"
Just (-3.0e16)
like image 155
leftaroundabout Avatar answered Nov 11 '22 19:11

leftaroundabout


As Somebody said, you can create a helper function (or three) to help transform the number into a format that works with read. I'm not the best with Haskell, so I'm not sure what other solutions exist, but I wrote some functions to help. I've tested it with read, and everything seems to work fine so far.

prefixZero :: String -> String
prefixZero ""         = ""
prefixZero ('-' : xs) = '-' : '0' : xs
prefixZero s          = '0' : s

suffixZero :: String -> String
suffixZero ""                    = ""
suffixZero ('.' : exp@('E' : _)) = '.' : '0' : exp
suffixZero (x : xs)              = x : suffixZero xs

format :: String -> String
format = suffixZero . prefixZero

You can call the following:

read (format "-1.E34") :: Double

Which outputs the following:

-1.0e34
like image 42
Jacob G. Avatar answered Nov 11 '22 18:11

Jacob G.