1

I have an expression: ((12 / 10) - 1) * 10)

I need to floor the result to convert it into an int. When I do that with math.floor(), I get different results:

-- a. Unfloored value
print(((12 / 10) - 1) * 10) -- 2.0

-- b. Floored value (unexpected result)
print(math.floor(((12 / 10) - 1) * 10)) -- 1

-- c. Floored result of unfloored value (expected result)
print(math.floor(2.0)) -- 2

I would like to know why this is happening and if there is a way to fix it (to get c.)

I've tried putting the expression in a variable and then flooring it (resulted in 1), putting the division portion of the expression into it's own variable (resulted in 2.0), and I also tried putting each portion of the expression into it's own variable and then flooring it (resulted in 1). None of these outputted 2, the expected value.

1
  • This question is similar to: Is floating-point math broken?. If you believe it’s different, please edit the question, make it clear how it’s different and/or how the answers on that question are not helpful for your problem. Commented Aug 26 at 4:32

1 Answer 1

1

What you have observed is due to the imprecision of floating-point numbers. By default Lua uses 64-bit floats which have about 16 decimal digits of precision.

The expression ((12 / 10) - 1) * 10 doesn't have an exact binary floating-point representation, because 12/10 doesn't have an exact binary floating-point representation, or more fundamentally, 1/5 doesn't have an exact binary floating-point representation. Here is a classic paper on the topic.

> ((12 / 10) - 1) * 10
2.0

Here it only looks like the result is 2.0 because that is what the printed representation of the result indicates. But looking more closely:

> string.format("%.16f", ((12 / 10) - 1) * 10)
1.9999999999999996

Now it can be seen that the actual value of the expression ((12 / 10) - 1) * 10, when computed using floating-point arithmetic, is in fact less than 2.0. This is the reason that the floor of this expression is 1.

How would I get 2 from this almost-2 number?

In general this requires more detailed information about the nature of the calculations and the origins of the numbers used. This is the topic of the field called Numerical Analysis.

A coarse solution would be to add an epsilon value to the result before flooring it to correct for floating-point error. This epsilon should be as small as possible to avoid raising values which are slightly less than an integral value above that value.

An epsilon of 1.0e-15 is small enough in this case that it should not affect many results, but note that this epsilon would vary for other calculations. I am assuming here that the input numbers a, b, c, and d are integers, but if any of these are floating-point values the epsilon may need to be adjusted to account for additional floating-point error.

function f (a, b, c, d, epsilon)
   local epsilon = epsilon or 1e-15
   local result = ((a / b) - c) * d
   return math.floor(result + epsilon)
end

For the posted expression of ((12 / 10) - 1) * 10:

> f(12, 10, 1, 10)
2

One comment below suggests adding 0.5 to the result before flooring: do not do this as it will cause errors in many cases. Consider the expression ((37 / 25) - 1) * 10 which is very near (but not exactly) 4.8.

With a small epsilon (1e-15) the correct result is obtained:

> f(37, 25, 1, 10)
4

But with an epsilon of 0.5 an incorrect result is obtained:

> f(37, 25, 1, 10, 0.5)
5
Sign up to request clarification or add additional context in comments.

4 Comments

How would I get 2 from this almost-2 number? More specifically in a case where I don't "know" the number I want, eg: I get almost-x from x = math.floor(value), how would I get the correct x?
Add 0.5 and then take the floor.
@user207421 -- Adding 0.5 before flooring will give incorrect results in many cases. Consider ((37 / 25) - 1) * 10 => 4.79999.... The floor is 4, but after adding 0.5 the floor is 5.
@CrashTestJava -- I added some material about handling these sorts of floating-point errors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.