More accommodating read for scientific notation in Haskell
Asked Answered
A

2

7

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. . . .

Azal answered 28/4, 2018 at 16:13 Comment(2)
Maybe write another function which parses the string to an equivalent format which can be used with read?Phyllotaxis
I always thought Haskell should support such literals. There are reasons not to do it in the language, but I really don't see a reason not to support it in read. Fortunately, defining a numbers parser yourself is easy (and better than preprocessing for Read).Bomke
A
3

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
Antwanantwerp answered 28/4, 2018 at 19:13 Comment(0)
B
3

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)
Bomke answered 28/4, 2018 at 20:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.