basvandijk / scientific

Arbitrary-precision floating-point numbers represented using scientific notation
BSD 3-Clause "New" or "Revised" License
72 stars 40 forks source link

base10Exponent conversion #43

Open lippling opened 7 years ago

lippling commented 7 years ago

I want to store a scientific value in the database using a predefined base (e.g. 2.56 as 256 with predefined exponent of -2). When I retrieve the value from the database I construct the scientific value with scientific 256 (-2). I also get values from the JSON parser and they have arbitrary exponents, e.g. 2.8987623864723 has exponent -13. How can I convert the scientific value with exponent -13 to -2 so it has the right coefficient which can be saved to database? I haven't found a utility function for that.

basvandijk commented 7 years ago

As mentioned on /r/haskell:

I'm not entirely clear on what you want to accomplish. But if you want to convert scientific values to a fixed precision you can just use Data.Fixed as in:

> realToFrac (2.8987623864723 :: Scientific) :: Fixed E2
2.89

Do be aware that realToFrac can be dangerous when applied to a Scientific coming from an untrusted source. See the docs for details.

lippling commented 7 years ago

Sorry for double posting, I was not sure if issues are read.

I get a float/double from an unstrusted source (JSON) which gets parsed into a scientific value (aeson). I want to store it into a database column as an INT with a predefined exponent.

For example:

2.7864 -> parse -> convert to 27864 -> save to db 2.7 -> parse -> convert to 27000 -> save to db 2.78648 -> parse -> convert to 27865 or 27864 -> save to db 2 -> parse -> convert to 20000 -> save to db

And of course I need to use it the other way around: fetch from db, do some calculations, send back to client as json number.

basvandijk commented 7 years ago

I'm thinking of adding the following function:

toBoundedFractional
    :: (Fractional a)
    => Integer -- ^ low bound
    -> Integer -- ^ high bound
    -> Scientific
    -> Maybe a
toBoundedFractional lo hi s
    | c == 0 = Just 0
    | e >  limit && e > hiLimit = Nothing
    | e < -limit && e < loLimit && e + d < loLimit = Just 0
    | otherwise = Just $ realToFrac s
  where
    c = coefficient s
    e = base10Exponent s

    d = integerLog10' (abs c)

    loLimit = integerLog10' lo
    hiLimit = integerLog10' hi

You can then define the helper function:

toIntBoundedFixed :: (HasResolution a) => Scientific -> Maybe (Fixed a)
toIntBoundedFixed = toBoundedFractional (toInteger (minBound :: Int))
                                        (toInteger (maxBound :: Int))

and a resolution type for your desired resolution:

data E4
instance HasResolution E4 where
    resolution _ = 10000 -- | resolution of 10^-4 = .0001

Then you can use it as follows:

λ (\(MkFixed i) -> i) <$> (toIntBoundedFixed 2.7864 :: Maybe (Fixed E4))
Just 27864

I haven't thought this through properly yet so the API might change.

Would this work for you?

lippling commented 7 years ago

Sorry for the delay. Right now I'm busy with another issue, but I'll come back to it in a few days and give feedback.