How to safely floor or ceil a CGFloat to int?
Asked Answered
D

3

24

I often need to floor or ceil a CGFloat to an int, for calculation of an array index.

The problem I permanently see with floorf(theCGFloat) or ceilf(theCGFloat) is that there can be troubles with floating point inaccuracies.

So what if my CGFloat is 2.0f but internally it is represented as 1.999999999999f or something like that. I do floorf and get 1.0f, which is a float again. And yet I must cast this beast to int which may introduce another problem.

Is there a best practice how to floor or ceil a float to an int such that something like 2.0 would never accidentally get floored to 1 and something like 2.0 would never accidentally get ceiled to 2?

Durga answered 16/8, 2012 at 19:16 Comment(1)
This question cannot be answered correctly until specifications are provided describing the error in the input value and what the consequences are of returning values that are too high or too low. There are trade-offs between returning a value that is too high because previous rounding-error made the value too high and returning a value that is too low because the value was artificially adjusted to compensate for the preceding error, and vice-versa. Without explanation of context, there is no single correct answer.Habergeon
M
22

There are a couple misconceptions in your question.

what if my CGFloat is 2.0f but internally it is represented as 1.999999999999f

can't happen; 2.0, like all reasonably small integers, has an exact representation in floating-point. If your CGFloat is 2.0f, then it really is 2.0.

something like 2.0 would never accidentally get ceiled to 2

The ceiling of 2.0 is 2; what else would it possibly be?


I think the question that you're really asking is "suppose I do a calculation that produces an inexact result, which mathematically should be exactly 2.0, but is actually slightly less; when I apply floor to that value, I get 1.0 instead of 2.0--how do I prevent this?"

That's actually a fairly subtle question that doesn't have a single "right" answer. How have you computed the input value? What are you going to do with the result?

Moreover answered 16/8, 2012 at 19:33 Comment(0)
D
34

Swift supplemental answer

I am adding this as a supplemental answer for those who come here looking how to use floor and ceil with a CGFloat (like I did).

var myCGFloat: CGFloat = 3.001
floor(myCGFloat) // 3.0
ceil(myCGFloat) // 4.0

And if an Int is needed, then it can be cast to one.

var myCGFloat: CGFloat = 3.001
Int(floor(myCGFloat)) // 3
Int(ceil(myCGFloat)) // 4

Update

There is no need to use the C floor and ceil functions anymore. You can use the Swift round() with rounding rules.

var myCGFloat: CGFloat = 3.001
myCGFloat.round(.down) // 3.0
myCGFloat.round(.up) // 4.0

If you don't wish to modify the original variables, then use rounded().

Notes

  • Works with both 32 and 64 bit architectures.

See also

Dragnet answered 15/12, 2015 at 9:41 Comment(0)
M
22

There are a couple misconceptions in your question.

what if my CGFloat is 2.0f but internally it is represented as 1.999999999999f

can't happen; 2.0, like all reasonably small integers, has an exact representation in floating-point. If your CGFloat is 2.0f, then it really is 2.0.

something like 2.0 would never accidentally get ceiled to 2

The ceiling of 2.0 is 2; what else would it possibly be?


I think the question that you're really asking is "suppose I do a calculation that produces an inexact result, which mathematically should be exactly 2.0, but is actually slightly less; when I apply floor to that value, I get 1.0 instead of 2.0--how do I prevent this?"

That's actually a fairly subtle question that doesn't have a single "right" answer. How have you computed the input value? What are you going to do with the result?

Moreover answered 16/8, 2012 at 19:33 Comment(0)
M
4

EDIT - read the comments for reasons why this answer isn't right :)

Casting a float to an int is an implicit floorf i.e. (int)5.9 is 5. If you don't mind that then just cast :)

If you want to round up then just cast the result of a ceilf - casting after rounding shouldn't introduce any errors at all (or, if you want, add one before casting i.e. `(int)(5.9+1)' is '6' - same as rounding up).

To round to the nearest, just add 0.5 - (int)(5.9+0.5) is 6 but (int)(5.4+0.5) is 5. Though I would just use roundf(5.9) :)

Monger answered 16/8, 2012 at 19:27 Comment(2)
Casting float to int is not an implicit floor. It is, in C and some other common languages, a truncation (toward zero). Floor is rounding to the nearest integer in the direction of negative infinity. Converting -3.5 to integer yields -3, but floor(-3.5) is -4.Habergeon
OK, that's a fair point - I'd oversimplified to the point of just being wrong :)Monger

© 2022 - 2024 — McMap. All rights reserved.