2

I want to generate an array of float. For instance, this one:

[0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

So, I use this tiny line:

(0.5..2).step(0.1).to_a

But it produces this instead:

[0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2000000000000002, 1.3, 1.4, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2.0]

What have I done wrong?

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
sidney
  • 2,704
  • 3
  • 28
  • 44
  • 3
    You've done nothing wrong. The `1.1` you got isn't actually 1.1 either. – tmyklebu Oct 07 '14 at 15:13
  • Arithmetic loops on floating point is a very common question whatever the language... For example http://stackoverflow.com/questions/21735083/floating-point-error-mess/21737478#21737478 or http://stackoverflow.com/questions/24356970/calculating-the-function-and-checking-its-domain/24374791#24374791 and surely many others – aka.nice Oct 08 '14 at 11:38

4 Answers4

6

Floating point numbers like 0.1 can not be represented precisely. Using floating pointer numbers as step would give you unexpected result like that.

A better alternative is:

(5 .. 20).map {|e| e / 10.0}
#=> [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
Justin Wood
  • 9,941
  • 2
  • 33
  • 46
Yu Hao
  • 119,891
  • 44
  • 235
  • 294
3

Use Rational Literals, Then Map to Floats

Floating point numbers can bite you. Specifically, binary can't represent 0.1 accurately. Ruby has a number of classes for dealing accurately with arbitrary-precision numbers, including BigDecimal and Rational.

You can use the new 2.1 syntax for rational literals to create your series of floats. For example:

(0.5r..2r).step(0.1r).map &:to_f
#=> [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

Doing it this way may look a little more cluttered than the currently-accepted answer, but this approach is applicable to a wider range of precision problems where dividing by 10.0 is not the solution.

Community
  • 1
  • 1
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • 1
    Well, this way is much more beautiful, I accept it instead, for the future viewers of this question, but it requires ruby > 2.1 indeed – sidney Oct 07 '14 at 15:04
1

I would do as @YuHao suggests--clean and easy-to-read--but I would like to point out that you could also use the BigDecimal class:

require 'bigdecimal'

v = BigDecimal.new(0.5, 1)
a = (20-5+1).times.with_object([]) { |_,arr| arr << v.to_f; v += 0.1 }
  #=> [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2,
  #    1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]

Notes:

  • When initializing a BigDecimal object with a Float, the precision must be given (here that is , 1).
  • If v is an instance of BigDecimal, so is v + 0.1.
  • BigDecimal has no succ method, so iteration is not possible. Hence, one cannot just map a BigDecimal range into an array of Floats. (Ranges of BD's are permitted, though I question the utility of a range that can't be iterated.)
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

An additional problem,how to get this result(Retained 2 decimal places)

[0.50, 0.60, 0.70, 0.80, 0.90, 1.00, 1.10, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00]

bluexuemei
  • 233
  • 3
  • 12