Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
Let's save I have a value:
Value! = 10.3
and I want to determine if it's an integer.
IF Value! = INT(Value!) THEN
' Value! is an INTEGER
END IF
Is this the best way to determine that a number is an integer?
I ask because while perusing other programming language sites I ran across people taking about how this method should NOT be used because of floating point anomalies. They suggest using things like InstanceOf, a combination of ABS and .Floor, and other methods. I realize these examples are commands used with other languages.
Could any "floating point anomalies" with QB64 affect the method I have shown above?
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Posts: 303
Threads: 10
Joined: Apr 2022
Reputation:
44
I would look at it a different way - if the value is supposed to be an integer, why aren't you using an integer variable from the start? And if it won't always be an integer, why do you care if it's specifically an integer (vs. just close to an integer)?
(07-25-2024, 07:40 PM)TerryRitchie Wrote: Could any "floating point anomalies" with QB64 affect the method I have shown above? Yes, the issues are common to all languages that use the IEEE-754 representation of floating-point values (which is most of them).
The key issue is that even if your floating-point values start out as integers, they might not still be integers when you go to check them. Ex. Operations that should round-trip back to an integer may not do so due to floating-point error, such as several division and multiplication steps. In general it's very hard to guarantee this won't happen, and much easier to simply allow for it to happen and check for values within a certain tolerance of the expected value.
Posts: 2,171
Threads: 222
Joined: Apr 2022
Reputation:
103
07-26-2024, 12:23 AM
(This post was last modified: 07-26-2024, 12:39 AM by Pete.)
Yeah, these things can be real pains...
Code: (Select All)
Do
Input "Input a number: "; value!
If InStr(Str$(value!), ".") = 0 Or InStr(Str$(value!), ".") And Val(Mid$(Str$(value!), InStr(Str$(value!), ".") + 1)) = 0 Then Print "The number is an integer." Else Print "The number is not an integer."
Loop
Looks like it would work, right? Wrong.
10.01 - is not an integer. Okay...
10.001- is not an integer. Okay...
10.0001 - is not an integer. Okay...
10.00001 - is not an integer. Okay...
10.000001 - is an integer. OOPS!!!!!!!!
Taking it to the limits reveals the floating point problems we get in base2 math.
Edit: For fun, it does work if we make it a sting in the first place...
Code: (Select All)
Do
Input "Input a number: "; a$
If InStr(a$, ".") = 0 Or InStr(a$, ".") And Val(Mid$(a$, InStr(a$, ".") + 1)) = 0 Then Print "The number is an integer." Else Print "The number is not an integer."
Loop
Pete
Posts: 1,272
Threads: 119
Joined: Apr 2022
Reputation:
100
07-26-2024, 02:02 AM
(This post was last modified: 07-26-2024, 02:06 AM by TerryRitchie.)
(07-26-2024, 12:17 AM)DSMan195276 Wrote: I would look at it a different way - if the value is supposed to be an integer, why aren't you using an integer variable from the start? And if it won't always be an integer, why do you care if it's specifically an integer (vs. just close to an integer)?
(07-25-2024, 07:40 PM)TerryRitchie Wrote: Could any "floating point anomalies" with QB64 affect the method I have shown above? Yes, the issues are common to all languages that use the IEEE-754 representation of floating-point values (which is most of them).
The key issue is that even if your floating-point values start out as integers, they might not still be integers when you go to check them. Ex. Operations that should round-trip back to an integer may not do so due to floating-point error, such as several division and multiplication steps. In general it's very hard to guarantee this won't happen, and much easier to simply allow for it to happen and check for values within a certain tolerance of the expected value. The reason I was looking into this was the need to test if a SINGLE value is an INTEGER. I developed a roto-zoom function that uses a COS/SIN lookup table if integers are passed and uses traditional trig if non-integers are passed. The function performs many times faster if only integers are used but I've noticed hiccups here and there in the speed which led to me to those discussions on other web sites. General rotation of a ship in games is easily done with integer degrees. But having an enemy ship point directly at the player ship from a distance all the way across the screen would require more precision than integers could provide, for instance. Hence, the reason I developed the hybrid roto-zoom routine.
Another use case for using SINGLE values where INTEGERs are used is screen coordinates. When calculating the positions, vectors, and magnitudes of objects you must use SINGLE values for accuracy but then use the INTEGER portion for screen placement.
From what Pete has shown perhaps a very small threshold check should somehow be implemented? I kept seeing code on the other sites implementing 1e-5 which I believe is part of their threshold check. That didn't make much sense to me until Pete's explanation.
Yeah Pete, there were also those on the other sites showing what did, convert to a string first. Which again, I thought was rather odd until your explanation.
New to QB64pe? Visit the QB64 tutorial to get started.
QB64 Tutorial
Posts: 303
Threads: 10
Joined: Apr 2022
Reputation:
44
(07-26-2024, 02:02 AM)TerryRitchie Wrote: The reason I was looking into this was the need to test if a SINGLE value is an INTEGER. I developed a roto-zoom function that uses a COS/SIN lookup table if integers are passed and uses traditional trig if non-integers are passed. The function performs many times faster if only integers are used but I've noticed hiccups here and there in the speed which led to me to those discussions on other web sites. General rotation of a ship in games is easily done with integer degrees. But having an enemy ship point directly at the player ship from a distance all the way across the screen would require more precision than integers could provide, for instance. Hence, the reason I developed the hybrid roto-zoom routine.
Another use case for using SINGLE values where INTEGERs are used is screen coordinates. When calculating the positions, vectors, and magnitudes of objects you must use SINGLE values for accuracy but then use the INTEGER portion for screen placement.
From what Pete has shown perhaps a very small threshold check should somehow be implemented? I kept seeing code on the other sites implementing 1e-5 which I believe is part of their threshold check. That didn't make much sense to me until Pete's explanation.
Yeah Pete, there were also those on the other sites showing what did, convert to a string first. Which again, I thought was rather odd until your explanation. In that case I would check for an integer within a tolerance (Ex. within `.00001` or something similar), that should be fine since an extra `.00001`in either direction is unlikely to change the answer enough to matter. The tolerance range itself doesn't need to be all that exact, the error should generally be smaller than that number I gave and in the worst case you'll just fall back to the floating-point version and thus still get the right answer.
I suppose alternatively you could multiple the number by 10 or 100 and increase the size of your table for higher accuracy, but that might make it prohibitively large.
For something like screen coordinates this issue doesn't really matter because you're going to round to the nearest integer in the end and use whatever you get. The sin/cos situation is special because you want to do something different if the number isn't an integer vs. just chop off the fractional part.
Posts: 2,171
Threads: 222
Joined: Apr 2022
Reputation:
103
07-26-2024, 02:41 AM
(This post was last modified: 07-26-2024, 02:43 AM by Pete.)
@Jack
Jack did some amazing work with bit flipping for large number calculations. He might have some further non-string advice to share, if he sees this post.
Pete
Posts: 422
Threads: 27
Joined: Apr 2022
Reputation:
26
Hello Pete
I think that Terry's opening code is probably the simplest and perhaps fastest
Posts: 246
Threads: 44
Joined: May 2022
Reputation:
28
I use these methods (versus the ones listed here):
If Value MOD 1 = 0 -> INTEGER
If Value \ 1 = Value -> INTEGER
maybe it's the same in the background, just written differently. I didn't do this using a string conversion because those conversions are slow.
Posts: 13
Threads: 1
Joined: May 2024
Reputation:
3
strange programming system. i ran my own test.
Code: (Select All)
DIM nf AS SINGLE, ni AS _INTEGER64, tol AS _INTEGER64
nf = 10000.0
ni = 10000
tol = 1E+10
IF FIX(nf * tol) = ni * tol THEN PRINT "They are basically equal."
PRINT FIX(nf * tol)
PRINT ni * tol
the message is not printed in program run. change function to INT, however and it is "fixed." also note that in both cases, the function result printed is "1E+14". it might be i'm running qb64 phoenix 3.11 and should upgrade and try again. then i could put double ampersand on the two variables declared _integer64.
in one program i wrote. i was forced to do a lot like pete. turn it into string and check to see if there's decimal point or "E-" or "D-". this was to create lisp-like code which only knows "E" instead of "D" for scientific notation.
Posts: 422
Threads: 27
Joined: Apr 2022
Reputation:
26
07-26-2024, 05:25 PM
(This post was last modified: 07-26-2024, 05:29 PM by Jack.)
Petr
I like If Value MOD 1 = 0 -> INTEGER but it remains to be tested whether it's faster or slower than using INT
integer division is rather slow especially when dealing with 64-bit integers
but in this case with a divisor of 1 gives g++ an excellent opportunity to optimize, so the performance is probably good
|