Float and Int Variables: Casting and Other Issues

Nov 30, 2011

To programmers with experience making games, this may seem like something that doesn’t need to be covered. As I’ve pointed out before though, that’s a sign of a common barrier to people getting started: it’s a detail which is quite unnatural, however because it seems obvious to people that are used to it, it is rarely brought up.

One of the first things learned in programming is that there are different variable types for numbers with decimal precision (typically of type float, double, or number) versus numbers without decimal precision (almost always using the type int, short for integer).

For readers still getting used to the distinction, numbers with decimal precision include:

0.0
-1.5
3.14159265
9000.36
5.0
57.3333333

Meanwhile, examples of int values include:

0
-2
3
9000
5
57

Note that a number with decimal precision can have a decimal part of .00000… but it’s of a different type because it could have a decimal part assigned, not because it currently does. As a departure from pure math terminology, simply setting a float value to 6.0 does not change that float value to an integer in the programming sense.

For this reason, in programming putting a “.0” after a number communicates to the compiler (and other programmers) that a number is intended as a float/number with decimal precision, not as type int. I use that notation throughout this entry. 300 is different from 300.0, because 300 is of type int whereas 300.0 is of type float.

Don’t Worry About Performance (Yet)

While getting started out, and well into making a number of games, I’d suggest not worrying about performance when deciding what types of variables to use.

Decades ago, before floating point units were common in processors, using int for nearly everything was necessary for performance. Computer science trickery called fixed-point math enabled programmers to simulate decimal accuracy, but at the expense of reduced precision and range. That technique is still sometimes needed for making games for cellphones of the non-smartphone variety.

Anymore, calculations with floats are nearly the same speed as integer math, and in either case it’s done fast enough to not be a primary performance concern. The minor performance differences now come down to cache complexities and frequency of casting between types, both of which are dramatically overshadowed by other issues.

Especially with the number of objects being juggled for a typical hobbyist computer game (dozens of moving things, rather than thousands), basic variable choice is unlikely to be a source of problems.

Avoid Checking Equality on Floats

Float values do not have infinitely fine precision. One effect of this is that a series of math operations which, done on paper, should equal exactly 5.0, may on a computer yield a number extremely close to, but not, that value, ex. 4.999999999 or 5.00000001. Rather than checking:

if(someDecimalVariable == 160.0)

…which risks being false due to small but accumulating precision errors, something along the lines of this is safer:

if( absoluteValue(someDecimalVariable-160.0) <= 0.1)

...since it allows for a range of minor error. I am using absoluteValue(number) as a generic math function for absolute value, though depending on language it's often of the form abs(number) or Math.abs(number).

Note that if context allows, inequalities are much safer than exact equality, i.e. it's perfectly fine to check:

if(someDecimalVariable >= 160.0)

...or...

if(someDecimalVariable <= 160.0)

Suitability of Each Type

If it can be counted in definite pieces, use an int. This typically applies to ammo, score, or the number of anything (enemies, particles, etc.). Indexes into an array always need to be done by integer - for an array with 8 values in it, it makes since to access someArray[4] or someArray[5], but there's no value saved at index 4.3, meaning someArray[4.3].

Health can be either, depending on how the game works. If the healthbar is presented as a percentage, as is often the case of a 1-on-1 fighting game, decimal precision is an option. If health is discrete units - for example each tank has 5 boxes of health (even if perhaps a big explosion might take away 2 or 3 at once), saving the health as an int makes more sense.

Positions and speeds are the most common values to be stored and manipulated with decimal precision. A player's character might have position (10.2, 200.0) - whether as the .x and .y on a Point/Vector object or as two separate variables like positionX and positionY - and velocity (3.25, 0.0) - likewise either on a Point/Vector object or split into velX and velY. Motion would be simulated by adding the velocity to the position each frame, so that in the next frame the player will be at (13.55,200.0), creating the illusion of movement. Keyboard or gamepad controls, drag calculations, and other effects on player movement would then modify the velocity rather than affecting position directly.

Problems with Int Movement

Using decimal precision for positions and velcoity can seem counter-intuitive at first. This is especially the case for position, since positions on screen in a 2D game are typically distinguished by pixel coordinates. A player's character might be drawn at position (15,200) one frame, then as they move right slightly, the character would be drawn instead at (16,200). There is no such thing as a pixel 15.3 from the left edge.

[Aside: using textured polygons and a generalized rendering approach, it's certainly possible to draw something at position (15.3,200.0) through a bit of pixel blurring/sampling, but this is being written regarding the typical beginning case of 2D games in which bitmap data gets copied to directly to video memory.]

The first time someone integrates keyboard controls into a project made from scratch, it typically begins by figuring out how to draw a graphic to the screen at a given position:

// note: this is pseudocode, these are not real function names

// blits graphicToDraw to position (drawAtX,drawAtY)
void draw(bitmap graphicToDraw, int drawAtX, int drawAtY);

// ...skipping over code, into main loop...

while(gameInPlay) {
  draw(playerGraphic, 125, 300);

  //  update screen from buffer
  // (not relevant to all languages)
  updateScreen();
}

/* reminder that for many languages, instead of
a while(gameInPlay) loop, this type of logic would
take place inside an update() function called by a
timer event ~30 times per second */

Realizing that a moving player character will need its position saved within variables, rather than hard coded, the code will then change into something like this:

int playerX = 125;
int playerY = 300;

// ...skipping over code, into main loop...

while(gameInPlay) {
  draw(playerGraphic, playerX, playerY);
  
  updateScreen();
}

This way, the position of the player's graphic can be changed by incrementing or setting the value of playerX and/or playerY, like so:

int playerX = 125;
int playerY = 300;

while(gameInPlay) {
  if(keyboard.leftArrowPressed) {
    playerX--;
    // above is short for playerX = playerX - 1;
  }
  if(keyboard.rightArrowPressed) {
    playerX++;
  }

  if(keyboard.upArrowPressed) {
    // recall that (0,0) is top-left
    // smaller Y in screen coords means higher
    playerY--;
  }
  if(keyboard.downArrowPressed) {
    playerY++;
  }
  
  draw(playerGraphic, playerX, playerY);
  updateScreen();
}

Based on the code above, the player character can be shuffled around the screen using arrow keys. This works, technically, but it's very jerky. There are three major shortcomings:

  • It's impossible to give the player a speed other than full pixel increments per cycle, i.e. its minimum speed is 1 pixel/frame.
  • It's impossible in this implementation to slow down gradually to a stop.
  • It's impossible to move the player at an arbitrary angle, for example 30 degrees or -114 degrees, while maintaining the same speed.

The solution to those problems is to use decimal precision (float or number) types instead of int for position.

float playerX = 125.0;
float playerY = 300.0;
float playerSpeed = 1.0;

while(gameInPlay) {
  if(keyboard.leftArrowPressed) {
    playerX -= playerSpeed;
    // short for playerX = playerX - playerSpeed;
  }
  if(keyboard.rightArrowPressed) {
    playerX += playerSpeed;
  }

  if(keyboard.upArrowPressed) {
    playerY -= playerSpeed;
  }
  if(keyboard.downArrowPressed) {
    playerY += playerSpeed;
  }
  
  draw(playerGraphic, playerX, playerY);
  updateScreen();
}

Now, we could set playerSpeed to 0.4 to move at only 40% the speed of 1 pixel/frame. We can increase the value of playerSpeed during the game ("playerSpeed += 0.2;" when we want speed to increase by 0.2 pixels per frame) to make the player move faster, and decay it gradually to make the player smoothly lose speed (using a line like "playerSpeed *= 0.98;" to lose 2% of our speed every frame). Since the trig operations sin() and cos() return values of decimal precision, we can now adjust playerX and playerY by those if we'd like to move the player by some arbitrary angle. [The details of these types of motions isn't the main topic here, but check out my tutorial on basic game motion for demonstration and source examples.]

Now there's just one problem: the compiler is probably griping at us with warnings about implicit casting between types, or even throwing an error for failing to manually between cast types (if I recall, C# tends to do this).

Casting Between Types

Casting is the operation of converting a value in memory from one type to another. For our purposes here, it means taking an int value like 5 and converting it to the float value 5.0, or in the other direction, taking a float value of 5.3 and converting it to an int value of 5.

Syntax for casting varies by programming language, and many programming languages support multiple ways to cast variables, but it often looks something like this:

float someDecimal = 5.6; // setting up a value to cast
int ourCastedValue = ((int)someDecimal); // 5
// note: cast drops decimal, doesn't round!

// other way:

int someInt = 6; // setting up a value to cast
float otherCastedValue = ((float)someInt); // 6.000000...

The cast does not need to be done when assigning a value, it can also be done in-line, so that we could update our draw code from earlier to look like this:

draw(playerGraphic, ((int)playerX), ((int)playerY) );

Doing it this way would remove the compiler's warning or error, since we're now specifically acknowledging that we deliberately mean to use a decimal-precision number as an int value here, i.e. that we are ok with losing the decimal portion of the value for this purpose.



Learn and practice team game development with Gamkedo Club.
Membership worldwide. Professional support. Proven process.




Subscribe by e-mail to receive weekly updates with Gamkedo.Community interviews and YouTube training videos for game developers!



2 Comments

  1. Nick says:

    I’m actually dealing with this issue in a game that i am working on right now, by using casting don’t we just lose the display precision, while maintaining a higher level of precision in the underlying model. Wont the jumpiness in the display of the object still be present?

    • Chris DeLeon says:

      Whether you’re losing any precision in the underlying model has to do with how or where you’re doing the cast, but you’re right that in most cases and intended uses it doesn’t make sense to overwrite the precise value that’s being cast from (unless the main reason for the cast is to truncate/round the number to a whole value).

      Jumpiness depends on your rendering process, but generally when it comes to straightforward blitting of bitmaps to the screen buffer or drawing art per-pixel, the reason we do a cast is because those draw arguments typically want integers because a pixel is either one color or another, it’s either drawn 1 pixel to the left or 1 pixel to the right, and there’s no finger resolution than that possible on the display anyhow. Jumpiness in this case is largely avoided by the granularity of high resolution displays – pixel-by-pixel inching forward might look like checkers movements on an ancient display, but on a modern display the dot sizes are small enough we tend to see the illusion of continuous motion when something jumps even a few or more of them at a time.

      An exception to this, and why it depends on the rendering technology being used, is that if what you’re drawing is textured polygons with OpenGL or something rather than blitting bitmaps from memory, then those processes often DO have something equivalent to “moving an image by half a pixel” in which actual values get calculated from blending between adjacent data points in the bitmap. In that sort of circumstance though casting generally isn’t even necessary though, as then full precision double or float values can typically be handed directly to the graphics API calls to be handed over to the graphics hardware.

Leave a comment

Comments Form
  • Your email will not be used for any purpose other than to update you in replies to your comments. Your website address will be shown as a link from your name with your comment. Your profile photo is auto-magically provided by Gravatar.

All contents Copyright ©2017 Chris DeLeon.

Site production by Ryan Burrell.