How can I trust casting from double to integer?
Asked Answered
T

8

7

I've been working on some simple code for creating histograms and found that following code:

double value = 1.2;
double bucketSize = 0.4;
double bucketId = value / bucketSize;

std::cout << "bucketId as double: " << bucketId << std::endl;
std::cout << "bucketId as int: " << int(bucketId) << std::endl;

results in crazy output of:

bucketId as double: 3
bucketId as int: 2

which basically ruins my trust in computers ;) when looking for the right bucketId for the value while creating a histogram.

I know that there are rounding errors etc. but is there any general solution to that problem?

(Just in case) Please don't suggest adding 0.5 to result of the division before casting to int as apparently it doesn't work very well in some cases (e.g. double value = 3; double bucketSize = 2;)

Thanks in advance.

Tichonn answered 21/10, 2014 at 13:55 Comment(5)
What do you want 3/2 to be?Byre
Integer part of the result so 3 for 1.2/0.4 and 1 for 3/2. Therefore std::round() will not help.Tichonn
"which basically ruins my trust in computers" -- Yeah, floating point tends to have that effect.Adrianople
First, print 1.2 and 0.4 with 20 digits after the decimal point. You might be up to some interesting discoveries. Next, read floating-point-gui.deByre
Adding 0.5 can fail in subtle ways, see my answer here for an example near the middle.Chasten
P
3

In the comments you say that you want the Integer part of the result. Well, unfortunately the double result of 1.2 / 0.4 just happens to be 2.9999999999999996 (On my machine. You can see the exact value with cout by using std::setprecision) and therefore the integer part of the result is 2. This, as you know, is because not all numbers can be represented with floating point numbers and because floating point operations incur errors.

Taking the integer part of a floating point number is on the same level as comparing floating point numbers with equality; You are not going to get consistent results. If you must have exact results, the general solution is to not use floating point numbers at all, but fixed point instead.

As with the equality comparison, you can work around the issue with an appropriate epsilon value. In this case you can add (or subtract if negative) a very small floating point number to the result before taking the integer part. The added number must be larger than the largest possible error the number might have but smaller than the smallest precision number that you must support (so that 9.999 doesn't become 10 if you must support down to 0.001 precision). Figuring out a good number for this can be quite hard.

Panegyrize answered 21/10, 2014 at 14:22 Comment(4)
My bad - by "result" I meant what results from math not from division done by machine :).Tichonn
@Tichonn Yes, I assumed that in my answer.Panegyrize
Thanks for your answer. I've found out that casting to float before casting to int provides some help here (my situation) but also is not a generic solution.Tichonn
@Tichonn That will depend on the actual values. Converting the results to float may help, but with other values, it may give a "wrong" result where you'd get the right result without the cast.Kushner
K
4

I'm basing this more or less on some of your comments to others. To get the integer part, the solution is to use modf. But the integer part of 1.2 / 0.4 could very well be 2, and not 3; 0.4 isn't representable in machine floating point (most of them, at least), so you're dividing by something very close to 0.4.

The real question is what you actually want. If you're looking to discretionize (does such a word exist) depending on bucketSize, then the correct way of doing this is to use scaled integers all around:

int value = 12;
int bucketSize = 4;
int bucketId = value / bucketSize;

and then:

std::cout << "bucketId as double: " << bucketId / 10.0 << std::endl;
std::cout << "bucketId as int: " << bucketId / 10 << std::endl;

Otherwise, if you want to keep the values as double, you will have to decide how close is close for the conversion to int, then use your own function:

int
asInt( double d )
{
    double results;
    double frac = modf( d, &results );
    if ( frac > 1.0 - yourEpsilonHere ) {
        results += 1.0;
    }
    return results;
}

It's up to you to decide what value is appropriate for yourEpsilonHere; it depends on the application. (The one time I used this techique, we used 1E-9. That doesn't mean that it's appropriate for you, however.)

Kushner answered 21/10, 2014 at 14:50 Comment(0)
K
3

Use std::lround. It returns the nearest integer to your double number.

#include<numeric>
double value = 1.2;
double bucketSize = 0.4;
double bucketId = value / bucketSize;

std::cout << "bucketId as int: " << std::lround(bucketId) << std::endl;

Note that 3.0/2.0 still might result in unexpected results, depending on whether the result is 1.4999998 or 1.5000001, naively spoken.

Kielty answered 21/10, 2014 at 14:3 Comment(3)
Well the problem is don't want "the nearest" - I need the integer part of the number. For result of 1.6 lround will give me 2 instead of expected 1.Tichonn
According to your description, you want the nearest. Otherwise, you should use (int) (1.2/0.4), since that exactly extracts the integer part of your number ... which is 2 on this machine and 3 on another.Kielty
To get the integer part, use modf. The problem is that you don't really want the integer part, because the integer part of 1.2 / 0.4 is 2, at least on your machine.Kushner
P
3

In the comments you say that you want the Integer part of the result. Well, unfortunately the double result of 1.2 / 0.4 just happens to be 2.9999999999999996 (On my machine. You can see the exact value with cout by using std::setprecision) and therefore the integer part of the result is 2. This, as you know, is because not all numbers can be represented with floating point numbers and because floating point operations incur errors.

Taking the integer part of a floating point number is on the same level as comparing floating point numbers with equality; You are not going to get consistent results. If you must have exact results, the general solution is to not use floating point numbers at all, but fixed point instead.

As with the equality comparison, you can work around the issue with an appropriate epsilon value. In this case you can add (or subtract if negative) a very small floating point number to the result before taking the integer part. The added number must be larger than the largest possible error the number might have but smaller than the smallest precision number that you must support (so that 9.999 doesn't become 10 if you must support down to 0.001 precision). Figuring out a good number for this can be quite hard.

Panegyrize answered 21/10, 2014 at 14:22 Comment(4)
My bad - by "result" I meant what results from math not from division done by machine :).Tichonn
@Tichonn Yes, I assumed that in my answer.Panegyrize
Thanks for your answer. I've found out that casting to float before casting to int provides some help here (my situation) but also is not a generic solution.Tichonn
@Tichonn That will depend on the actual values. Converting the results to float may help, but with other values, it may give a "wrong" result where you'd get the right result without the cast.Kushner
D
3

Maybe fixed decimal aproximation?

(int)(value * 100)/(int)(bucketSize *100)
Dickerson answered 21/10, 2014 at 14:30 Comment(0)
M
2

If you want 0.25 before it to return the next number (Like (double)1.75 to (int)2) use int(floor(buckedId+0.25)).

The thing is how much you want to make it round to the previous number.

Mcmillin answered 21/10, 2014 at 14:23 Comment(0)
G
2
#include<iostream>
#include <limits>
int main()
{
    double value = 1.200000000000000;
    double bucketSize = 0.4000000000000000;
    double bucketId = value / bucketSize;
    std::cout.precision(16);
    std::cout << "bucketId as double: " <<  std::fixed << bucketId << std::endl;
    std::cout << "bucketId as int: " << int(bucketId) << std::endl;
    return 1;
}

try this on your system you would something like

bucketId as double: 2.9999999999999996

bucketId as int: 2

and than with

std::cout.precision(15);

you would

bucketId as double: 3.000000000000000

bucketId as int: 2

this is happening because the precision limit of double is 15 , you could also try and engineer with long double and varying the precision.

Gunpowder answered 21/10, 2014 at 14:40 Comment(6)
For starters: double bucketSize = 0.4;, and then output bucketSize with a very large precision. (Although your implementation may not support a large enough precision for it not to appear as 0.4. But since 0.4 isn't representable in a double, that's certainly not its actual value.)Kushner
@JamesKanze, yes..you are right.. but why does precision of 15 that would of a double gives me 3.000000000000000 and that of more than that gives me 2.9....i suppose OP is facing same issue. i myself would like to learn. i thought this might help him out.Gunpowder
Because 2.9... rounded to 15 digits is 3.0? Using g++ (which does support very high precision output), I get value == 1.19999999999999995559, size == 0.40000000000000002220 and id == 2.99999999999999955591 when I output his values with 20 digits precision. With 15 or less digits, all round to his initial values.Kushner
And the precision limit of double is typically 17 decimal digits, not 15. Although it depends on what you mean exactly by precision: if you want to output the value, and be guaranteed of getting the same value back when you read it, you need 17 digits.Kushner
i referred this #9999721Gunpowder
The answers there are actually quite good. They point out the different meanings of decimal precision. Basically, if you have a decimal value, convert it into double, then back to decimal, anything over 15 digits may not give the same results. But if you have a double, and want to convert it to decimal and back, you need 17 digits.Kushner
F
2

I'd suggest something along the lines of

double d = 1.4 / 0.4;
int whole = (int)d;
int nextWhole = whole + 1;

int result = whole;

if (fabs(d - nextWhole) < EPSILON) result = nextWhole;

(this works for positive numbers)

Basically, if your number is so close to the next integer that it does not matter, this code will use the next integer.

Flighty answered 21/10, 2014 at 15:1 Comment(0)
C
1

You can use round()

http://www.cplusplus.com/reference/cmath/round/

I believe this always takes .5 numbers away from zero so if that's bad for your case it might not be the optimal solution

Copious answered 21/10, 2014 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.