In C and Objective-C, what really is the right way to truncate a float or double to an integer?
c, floating-accuracy, floating-point, objective-c
Solution
The truncate has been performed correctly, of course, but on an inaccurate intermediate value.
In general there's no way to know whether your `1.999999` result is a slightly inaccurate `2` (so the exact-maths result after truncation is `2`), or a slightly inaccurate `1.999998` (so the exact-maths result after truncation is `1`).
For that matter, for some calculations you could get `2.000001` as a slightly inaccurate `1.999998`. Pretty much whatever you do, you'll get that one wrong. Truncation is a non-continuous function, so however you do it, it makes your overall computation numerically unstable.
You could add an arbitrary tolerance anyway: `(int)(x > 0 ? x + epsilon : x - epsilon)`. It may or my not help, depending what you're doing, which is why it's a "hack". `epsilon` could be a constant, or it could scale according to the size of `x`.
The most common solution to your second question isn't to "remove the inaccuracy", rather to accept the inaccurate result as if it were accurate. So, if your floating point unit says that `(1.2-1)*10` is 1.999999, OK, it is 1.999999. If that value represents a number of minutes then it truncates to 1 minute 59 seconds. Your final displayed result will be 1s off the true value. If you need a more accurate final displayed result than that, then you shouldn't have used floating-point arithmetic to compute it, or perhaps you should have rounded to the nearest second before truncating to minutes.
Any attempt to "remove" inaccuracy from a floating-point number is actually just going to move inaccuracy around - some inputs will give more accurate results, others less accurate. If you're lucky enough to be in a case where the the inaccuracy is shifted to inputs you don't care about, or can filter out before doing the computation, then you win. In general though, if you have to accept any input then you're going to lose somewhere. You need to look at how to make your computation more accurate, rather than trying to remove inaccuracy in a truncation step at the end.
There's a simple correction for your example computation -- use fixed-point arithmetic with one base-10 decimal place. We know that format can accurately represent 1.2. So, instead of writing `(1.2 - 1) * 10`, you should rescale the computation to use tenths (write `(12 - 10) * 10`) and then divide the final result by 10 to scale it back to units.
Problem
I worked mostly with integers before, and in situations where I need to truncate a float or double to an integer, I would use the following before: ``` (int) someValue ``` except until I found out the following: ``` NSLog(@"%i", (int) ((1.2 - 1) * 10)); // prints 1 NSLog(@"%i", (int) ((1.2f - 1) * 10)); // prints 2 ``` (please see Strange behavior when casting a float to int in C# for the explanation). The short question is: how should we truncate a float or double to an integer properly? (Truncation is wanted in this case, not "rounding"). Or, we may say that since one number is 1.9999999999999 and the other is 2.00000000000001 (roughly speaking), the truncate is actually done correctly. So the question is, how should we convert a float or double so that the result is a "truncated" number that makes common usage sense? (the intention is not to use `round`, because in this case, for `1.8`, we do want the result of `1`, instead of `2`) Longer question: I used ``` int truncateToInteger(double a) { return (int) (a + 0.000000000001); } -(void) someTest { NSLog(@"%i", truncateToInteger((1.2 - 1) * 10)); NSLog(@"%i", truncateToInteger((1.2f - 1) * 10)); } ``` and both print out as `2`, but it seems too much of a hack, and what small number should we use to "remove the inaccuracy"? Is there a more standard or studied way, instead of such an arbitrary hack? (Note that we want truncation, not rounding in some usage, for example, say, if the number of seconds is 90 or 118, when we show how many minutes and how many seconds have elapsed, the minute should display as `1`, but should not be rounded up to `2`)