Reputation: 2239
I would like to create a list of all the real numbers between 1.0 and 2.0 in two-decimal-place increments. This
dList = [1.00,1.01..2.00]
however, creates float run-on problems
dList = [1.0,1.01,1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.1,1.11,1.12,
1.1300000000000001,1.1400000000000001,1.1500000000000001, ...
To remedy this I found what I thought was a function in Data.Decimal
namely, roundTo
. I was hoping to eventually run this
map (roundTo 2) [1.1,1.2..2.0]
and get rid of the float run-on, but it produces a giant error message. This page is for this beginner undecipherable. And so I'm trying to do this with an hs
file loaded at a ghci REPL. This is the code
import Data.Decimal
dList :: [Decimal]
dList = [1.00,1.01..2.0]
main = print dList
and it produces
Could not find module ‘Data.Decimal’
Lost, I am....
Upvotes: 1
Views: 494
Reputation: 2239
Note
This answer is an FYI for all of you beginners simply trying to follow along in a beginner Haskell book that has you typing code into a text editor, firing up the ghci REPL and doing :load my-haskell-code.hs
.
YMMV Solution
As can be gleaned above, Data.Decimal
is not a standard included Prelude sort of package. It must be loaded independently -- and no, it doesn't work to simply put import Data.Decimal
at the top of your code. As leftaroundabout
said in a comment above, the very simplest way for Haskell beginners not yet doing projects is to start ghci thusly
stack ghci --package Decimal
Of course YMMV depending on how you installed Haskell. I installed Haskell through the stack project management, hence, the stack
before ghci --package Decimal
. Another unique thing about my setup is I'm using Emacs org-mode's Babel code blocks, which is largely the same as the basic type-and-load way, i.e., non-project. I did try to alter Emacs's haskell-process-args-stack-ghci
which is in haskell-customize.el
by just adding --package Decimal
but it didn't work. Instead, I simply went to my bash command line and put in stack ghci --package Decimal
, then I restarted a separate org-mode Babel ghci and it worked. Now,
dList :: [Decimal]
dList = [1.00,1.01..2.00]
> dList
[1,1.01,1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.10,1.11,1.12,1.13,1.14,1.15,1.16,1.17,1.18,1.19,1.20,1.21,1.22,1.23,1.24,1.25,1.26,1.27,1.28,1.29,1.30,1.31,1.32,1.33,1.34,1.35,1.36,1.37,1.38,1.39,1.40,1.41,1.42,1.43,1.44,1.45,1.46,1.47,1.48,1.49,1.50,1.51,1.52,1.53,1.54,1.55,1.56,1.57,1.58,1.59,1.60,1.61,1.62,1.63,1.64,1.65,1.66,1.67,1.68,1.69,1.70,1.71,1.72,1.73,1.74,1.75,1.76,1.77,1.78,1.79,1.80,1.81,1.82,1.83,1.84,1.85,1.86,1.87,1.88,1.89,1.90,1.91,1.92,1.93,1.94,1.95,1.96,1.97,1.98,1.99,2.00]
no muss, no fuss. I killed the ghci and loaded it without the --package Decimal
and it still knew about Decimal
, so this change is recorded quasi-permanently in my ~/.stack
directory somewhere? Oddly, the bash ghci session doesn't know about the *haskell*
ghci session in org-mode. Also, when just using the Emacs haskell mode stand-alone for type-and-load, its ghci session also doesn't play well with the org-mode ghci. I've gone with the minimalist Emacs org-mode babel because it seems better than just lhs
literal Haskell. If anyone knows how to make literal Haskell sing like org-mode, I'd like to know.
Postmortem
I guess I insisted on figuring out Decimal
because in researching the whole rounding issue I started seeing a wild divergence of suggested solutions (not necessarily here, but other places) and scary technical arguments. Decimal
seemed the simplest in a wild storm of competing rounding strategies. Rounding should be simple, but in Haskell it turned into a time-sucking trip down multiple rabbit holes.
Upvotes: 3
Reputation: 120711
By far the simplest option in this case is
[n/100 | n<-[0..200]]
or variations on the same idea:
map (/100) [0..200]
(*1e-2) . fromIntegral <$> [0 .. 200 :: Int] -- (*) is more efficient than (/),
-- but will actually introduce rounding
-- errors (non-accumulating) again
No need for any special decimal library or rational numbers.
The reason this works better than the [x₀, x₁ .. xe]
approach is that integers below 252 can be expressed exactly in floating point (whereas decimal fractions cannot). Therefore, the range [0..200]
is exactly what you want it to be. Then at the end, dividing each of these numbers by 100 will still not give you exact representations of the hundredths you want to get – because such representations don't exist – but you will for each element get the closest possible approximation. And that closest possible approximation is in fact printed in x.yz
form, even with the standard print
function. By comparison, in [1.00,1.01..2.0]
you keep adding up already-approximate values, thereby compounding the errors.
You could also use your original range calculation in exact rationals, and only then convert them to float★ – this still doesn't require a decimal library
map fromRational [0, 0.01 .. 2]
Rational arithmetic is often an easy fix for similar problems – but I tend to advise against this, because it usually scales badly. Algorithms that have rounding problems in floating-point will more often than not run into memory problems in rational arithmetic, because you need to carry an ever-growing range of precision through the entire calculation. The better solution is to avoid needing the precision in the first place, like with the n/100
suggestion.
Also, arguably the [x₀, x₁ .. xe]
syntax is a broken design anyway; integer ranges have far clearer semantics.
Note also that the float errors in your original attempt aren't necessarily a problem at all. An error of 10-9 in a real-world measured quantity is for all meaningful purposes neglectable. If you need something to be truely exact, you probably shouldn't be using fractional values at all, but straight integers. So, conside Carl's suggestion of just living with the float-deviations, but simply printing them in a suitably rounded form, by way of showFFloat
, printf
, or Text.Show.Pragmatic.print
.
★Actually, in this specific case both solutions are almost equivalent, because converting Rational
to float involves floating-point dividing the numerator by the denominator.
In cases where you do need the Decimal
library (or some other library), you need to depend on it.
The easiest way to do this is to use Stack and add Decimal
to your global project. Then, you can load the file with stack ghci
and it'll know where to look for Data.Decimal
.
Alternatively, and IMO preferrably, you should create a project package yourself and only make that depend on Decimal
. This can be done with either Stack or Cabal-install.
$ mkdir my-decimal-project
$ cd my-decimal-project
$ cabal init
Now you're being asked some questions about the name etc. of your project, you can mostly answer with the defaults. Say your project defines a library (you can add an executable later on if you want.)
cabal init
creates amongst other things a my-decimal-project.cabal
file. In that file, add Decimal
to the dependency, and your own source file to exposed-modules
.
Then you need to (still in your project directory) cabal install --dependencies-only
to fetch the Decimal
library, and then cabal repl
to load your module.
Upvotes: 2