Sign in to follow this  
load_bitmap_file

Best way to see if a number is an integer

Recommended Posts

I feel like an idiot asking this but I don't think I ever found out the best way to see if a number is an integer and the darn GameDev search is down again (Does anyone know why? I really miss it when it's down). Meaning, if we have
#include <cmath>

int main()
{
float number1 = std::sqrt(36.0f);
float number2 = std::sqrt(19.0f);

return 0;
}

What's the best method for seeing if number1 and number2 are integers? I've been using a float and integer cast hack, but it's very ugly and there's got to be a better solution.

Share this post


Link to post
Share on other sites
Well, since most numbers aren't able to be represented exactly in floating point format you're best bet is to see if the real floating point value is within an acceptable range with respect to the nearest integer value.

Something like:

if(value - FLOOR(value) < epsilon || CIELING(value) - value < epsilon)
return true;

return false;


Where epsilon is your acceptable range.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster

bool IsInt( float Test )
{
if( float( Test ) == int( Test ) )
return true;
else
return false;
}



It's a bit of a slow-down from the cast, don't think it'll cause too much overhead though. If you don't need too much precision, then that's what I'd suggest, if you're doing some funky stuff to the float though, you're going to have some rounding problems and should prolly use the first answer

- Jordan

Share this post


Link to post
Share on other sites
For what purpose is this needed anyway? I'm supposing that your sqrt() example was merely that, an example. If you need 100% reliable mathematical accuracy, then you're probably going to have to really on more mathematical methods, such as keeping rational numbers in exact, fractional form (integer over integer, rather than floating point), keeping irrational numbers in their representative form (sqrt(2) stays as that, not as 1.4142...), etcetera. Which means that for things such as sqrt(36), you're just going to have to know that it is exactly 6, or write something to factor it into its prime components. Things like that.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ravyne
Well, since most numbers aren't able to be represented exactly in floating point format you're best bet is to see if the real floating point value is within an acceptable range with respect to the nearest integer value.

Something like:

if(value - FLOOR(value) < epsilon || CIELING(value) - value < epsilon)
return true;

return false;


Where epsilon is your acceptable range.


Quote:
Original post by Aprosenf
const float EPSILON = 0.00001f;

bool IsInt(float f)
{
return (fabsf(f - (float)(int)(f + EPSILON)) < EPSILON);
}


This method looks good, thanks.

Agony: The sqrt example was actually the thing I needed it for, but I'll definitely need to use it for more complex things eventually. Thanks everybody.

EDIT:
jflanglois: The google search doesn't as well as GameDev's but it's passable. I nearly forgot google can do that :]

Share this post


Link to post
Share on other sites
FYI
maybe it was just me but I remember once that FLOOR/CEIL ran incredibly slowly on my machine (considering what they did).. so the fabsf version is likly a lot better.

But then again this was a long time ago, and this predudice has stuck so I havn't tried it since [smile]

Share this post


Link to post
Share on other sites
Float to int is always a slow opperation. I believe theres finally a hardware instructuion for it in SSE3 though. Theres all sorts of interesting mathematical tricks to make it go as fast as possible though. Google for "fast float to int conversion" some time :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Ravyne
Float to int is always a slow opperation.


Okay, this is true in a general case... but it's not because the conversion itself is slow. Basically, the processor has several different rounding modes... the default is "round to nearest", but the C-standard requires "truncate towards zero". So the C-cast method requires the processor to save the current rounding state, set the rounding state to "truncate toward zero", perform the conversion, then restore the previous rounding state, all of which takes a good deal longer than simply doing the conversion.

If it just needs to be exact then the rounding mode doesn't matter... simply convert one way, then covert back, and check the result, it is actually fairly quick to do in assembly.


int is_int(float num)
{
int result = 0;
int true_val = 1;

__asm {
cvtss2si eax, num // round to int into eax
movss xmm0, num // move float into xmm0
cvtsi2ss xmm1, eax // convert int back to float in xmm1
cmpss xmm0, xmm1, 0 // test xmm0 vs xmm1
movmskps eax, xmm0 // eax != 0 if they are equal
cmp eax, 0
cmovne eax, true_val // make sure result == 1 if equal, 0 otherwise.
mov result, eax
}

return result;
}



this is the fastest way to do it mentioned... but I don't think speed is a concern. To test with a threshold value is similar, but the rounding mode must be saved and restored.

Share this post


Link to post
Share on other sites
Quote:
this is the fastest way to do it mentioned... but I don't think speed is a concern. To test with a threshold value is similar, but the rounding mode must be saved and restored.


Just curious... have you timed your converting function? If yes, what times have you got? I wonder whether it's faster then FloatToIntPositive/FloatToIntNegative from the code below (it's not my, I've taken it from GPG and modified sligthly).
And does it work for all numbers (regardless sign)?
If it's faster then I'll probably use your version :-)

I've timed mine and it looks that FloatToIntPositive/FloatToIntNegative are 5x times faster than casting and FloatToInt is only 2x faster then casting.




typedef union
{
int i; // as integer
float f; // as float

} INTORFLOAT;


INTORFLOAT ftoibiasp = {((23 + 127) << 23)};
INTORFLOAT ftoibiasn = {((23 + 127) << 23) + (1 << 22)};
INTORFLOAT ftmp;

// -------------------------------------------------

/*! they all _round_ to nearest integer, not throw away values after decimal point (like normal casting do)

ie.

0.1 -> 0
0.2 -> 0
0.3 -> 0
0.4 -> 0
0.5 -> 1
0.6 -> 1
0.7 -> 1
0.8 -> 1
0.9 -> 1
1.0 -> 1

*/


// ------------------------------------------------- start FloatToIntPositive

unsigned long int SC :: FloatToIntPositive(float _f)
{
ftmp.f = _f;
ftmp.f += ftoibiasp.f;
ftmp.i -= ftoibiasp.i;
return ftmp.i;
}

// ------------------------------------------------- end FloatToIntPositive

// ------------------------------------------------- start FloatToIntNegative

inline signed long int SC :: FloatToIntNegative(float _f)
{
ftmp.f = _f;
ftmp.f += ftoibiasn.f;
ftmp.i -= ftoibiasn.i;
return ftmp.i;
}

// ------------------------------------------------- end FloatToIntNegative

// ------------------------------------------------- start FloatToInt

signed long int SC :: FloatToInt(float _f)
{
return ( (_f < 0) ? (FloatToIntNegative(_f)) : (FloatToIntPositive(_f)) );
}
// ------------------------------------------------- end FloatToInt






[Edited by - Koshmaar on December 4, 2004 1:09:21 PM]

Share this post


Link to post
Share on other sites
Okay, this was a really fun problem :)

I optimized my code, I guess as much as I possibly can. Then I wrote a looping version that processes a stream of floats and writes to a stream of results.

The results were (tested with pipeline simulation on AMD code analyst): My single iteration version runs in 15 cycles. The code you posted runs in 14 cycles (if you don't count branch mis-prediction) and my loop-optimized code runs in 11 cycles per iteration.

I wrote the code to test over 100 floats, 1/3 of which were float version of integers, and 1/3 of which were negative. Your version suffered 28 branch-mispredictions, which cost 30 cycles each, bringing the average value to 21 cycles. My code suffered only the single unavoidable branch-mispredict at the end of the loop.

Here is the code and test harness:


void is_int(float* num, int* result)
{
__asm {
mov edx, [num]
mov esi, [result]
cvtss2si eax, [edx] // round to int into eax
movss xmm0, [edx] // move float into xmm0
cvtsi2ss xmm1, eax // convert int back to float in xmm1
cmpss xmm1, xmm0, 0 // test xmm0 vs xmm1
movss [esi], xmm1 // eax != 0 if they are equal
and dword ptr[esi], 1
}
}

void is_int(float* num, int* result, int count)
{
if(count <= 0)
return;

if(--count == 0)
{
is_int(num, result);
return;
}

__asm {
mov edx, [num]
mov esi, [result]
cvtss2si eax, [edx] // round to int into eax
movss xmm0, [edx] // move float into xmm0

start_loop:
add edx, 4
cvtsi2ss xmm1, eax
cvtss2si eax, [edx]
cmpss xmm1, xmm2, 0 // test xmm2 vs xmm1
movss xmm0, [edx]
movss [esi], xmm1 // xmm1 is 0xffff if equal,
movss xmm2, xmm0 // free up register 0
need to mask
and dword ptr[esi], 0001h
add esi, 4
sub count, 1
jnz start_loop

cvtsi2ss xmm1, eax // convert int back to float in xmm1
cmpss xmm1, xmm0, 0 // test xmm0 vs xmm1
movss [esi], xmm1 // xmm1 is 0xffff if equal, need to mask
and dword ptr[esi], 1
}
}

//////////////////////////////////////////////////////////////////
//The code you posted along with

void is_intSC(float* num, int* result)
{
*result = FloatToInt(*num) == *num;
}

//////////////////////////////////////////////////////////////////
// The test harness:

int results[100];
int resultsSC[100];
int resultsOpt[100];
float floats[100];

for(int i = 0; i < 100; i++)
floats[i] = (i + i/3.0f) * (i % 3 ? 1.0f : -1.0f);

for(int i = 0; i < 100; i++)
is_int(floats + i, results + i);

for(int i = 0; i < 100; i++)
is_intSC(floats + i, resultsSC + i);

is_int(floats, resultsOpt, 100);


for(int i = 0; i < 20; i++)
{
for(int j = 0; j < 5; j++)
std::cout << floats[i * 5 + j] << ": " << results[i * 5 + j] << " ";

std::cout << std::endl;

for(int j = 0; j < 5; j++)
std::cout << floats[i * 5 + j] << ": " << resultsSC[i * 5 + j] << " ";

std::cout << std::endl;

for(int j = 0; j < 5; j++)
std::cout << floats[i * 5 + j] << ": " << resultsOpt[i * 5 + j] << " ";

std::cout << std::endl;
}




And luckily, they all agree on all the numbers :)

Share this post


Link to post
Share on other sites
Thanks for great answer! :-) it looks like FloatToInt is certainly slower than your version, though there is still one unknown: if FloatToInt was so close to your optimized version and it was 2x slower than normal casting, then FloatToIntPositive and FloatToIntNegative (which were 5x faster then normal casting) should be faster then your version, in spite of having no "if" which makes cpu branching miss.

I wonder if you could time also theese two functions, using appropriate numbers (positive for FloatToIntPositive, negative for the second one)? Odds that they will be sligthly faster... and in spite of all, sometimes they can be useful, ie. when you are sure that this particular number will always be positive/negative - in that case you can gain few cycles (wow! micro optimization rocks ;->)

Anyway, I'm going to use your asm version in place of my old FloatToInt, so big thx again.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this