Can't solve 'Cars, Assemble!' because of floating point precision?

I’m participating in the 48in24 challenge, but to unlock ‘leap’ challenge I also need to complete this one first.
I believe my code should work, because I’ve checked others’ solutions and they’re very close to mine.

Here’s the code for the first task:

(ns cars-assemble)

(def amount 221)
(defn success-rate 
  [speed]
  (cond (== speed 0)  0.0
        (<= speed 4)  1.0
        (<= speed 8)  0.9
        (== speed 9)  0.8
        (== speed 10) 0.77
    ))

(defn production-rate
  "Returns the assembly line's production rate per hour,
   taking into account its success rate"
  [speed]
  (* amount (success-rate speed) speed)
  )

And only the last (6th) test fails with “Expected (= 1701.7 (cars-assemble/production-rate 10)) but got (not (= 1701.7 1701.7000000000003))”

I’ve thought it could be my error for using float values and I tried a little different approach:

(ns cars-assemble)

(def amount 221)
(defn success-rate 
  [speed]
  (cond (== speed 0)  (/ 0   100)
        (<= speed 4)  (/ 100 100)
        (<= speed 8)  (/ 90  100)
        (== speed 9)  (/ 80  100)
        (== speed 10) (/ 77  100)
    ))

(defn production-rate
  "Returns the assembly line's production rate per hour,
   taking into account its success rate"
  [speed]
  (float (* amount (success-rate speed) speed))
  )

Now 3/6 tests (number 4,5,6) are failing with very confusing error message:
“Expected (= 1701.7 (cars-assemble/production-rate 10)) but got (not (= 1701.7 1701.7))”

Solved.

Turns out, for some reason - order of multiplication matters:
(* 221 speed (success-rate speed)) passes all tests, but (* 221 (success-rate speed) speed) fails.
Maybe test should be changed to not so precisely test floats?

Just so you know, you can also disable Practice Mode (we’re making that clearer today) but learning more works too! :grin:

1 Like

I have tried your solution and didn’t work for me (but this could be an issue on my end). I used a workaround, but it’s not clean:

`(= speed 10) (- (* (* 221.0 0.77) speed) 0.0000000000003)`

Is this a Clojure “feature” or a limitation in the Exercism test setup?

Presuming the issue here is that the number aren’t rounded as you’re expecting, that’s an “issue” with computers, not clojure or Exercism: https://0.30000000000000004.com/

A part of this exercise is correctly translating between floats and integers. Take a look at the community solutions to see the idiomatic approaches: joaofnds's solution for Cars, Assemble! in Clojure on Exercism

1 Like

I, believe, there’s nothing wrong with Clojure. It’s just that floating point numbers have limited precision. You can test this in any language you like and most will get it wrong (maybe all, except raku, because it uses decimal numbers instead):

(221 * 10 * 0.77) == (221 * 0.77 * 10) // False!

But, I also believe, that it’s the issue with the test setup. It’s generally a bad practice to have normal comparison against floats, especially ==. It’s always better to use epsilon value or language-defined implementation of it (e.g. isCloseTo() in java). You can see proper implementation of this test in the Java track.

Further reading: The Floating-Point Guide - Comparison

1 Like

That’s correct, the tests should take care of floating point issues, especially when the only thing that needs to be changed is the order of operations. I’ve fixed a couple exercises in the past, this one was on the todo list but completely forgot to do it.

Anyway, here’s a PR that takes care of the issue:

@ErikSchierboom can you please review?

1 Like

Looks like I lost my maintainer privileges over the years, but lgtm, though the isCloseTo() idea from Java is compelling too…

Yeah, using epsilon is usually the way it is done. My approach just forces doubles into floats. Something like (= 1701.7 1701.7000000000003) which fails because it’s comparing two doubles, becomes (= float(1701.7) float(1701.7000000000003)) and now both numbers are floats. The second one will get truncated and lose its last digits and the check will pass.

This is more than enough and has worked great so far, as in Complex Numbers in Clojure on Exercism for example. People stopped coming up with all sorts of clunky solutions after the fix and the previous solutions all continued to work.

Anyway, let’s hope that someone re-opens this PR. I still need to fix that typo.