Text manipulation in Quake (part VI): Numbers

It’s been a while since we had one of these, and even I had to remind myself of the homework. The question was loaded – it assumed that you would add two state variables: S_Print_Red_Active and S_Print_Active. If you send a zero byte after printing in red, then you’ll close the stream and the second text will overwrite it. If you don’t send the zero byte to close it, S_Print will try to reopen the stream anyway, resulting in a garbage character being printed. The easy solution is to make both functions share the same state variable.

Today we’re going to plug one of the gaps between normal string handling and our new text streams. You may recall way back in the first post, we created a health monitor which excitingly used pacman dots to show display our health, instead of just printing the value using ftos and centerprint. Yet if we wanted to print the health value as digits using our new text system, we don’t have a way to do so.

Today we’re going to write T_Integer, a function which will take an integer, and output the characters to display it in decimal. This is both a useful tool, and our first example of a T_ function which is dynamic rather than static. First we’ll write the function to work on any positive input value, and worry about negative numbers later. The plan:

Step 1: Work out how many digits long the number is
Step 2: Get the leading digit of the number
Step 3: Print the character matching that digit
Step 4: Subtract that digit’s value from the number
Step 5: If digits remain, return to step 2

//Step 1: Work out how many digits long the number is
	local float exp;
	local float digit;
	exp = 10;
	while(exp <= num)
		exp = exp * 10;

We’re not recording the number of digits directly, we’re storing the next largest power of 10. When the loop finishes, the number of zeroes exp has (in decimal) is equal to the number of digits in num.

//Step 2: Get the leading digit of the number
	exp = exp / 10;
	digit = floor(num/exp);

If we divided num by exp right after Step 1, we’d get a number less than 1, which looks like 0.abcde... . Multiply that number by 10 and it looks like a.bcdce...,  where the whole number part is the leading digit we want. The floor function gives us that whole number part. Rather than multiply by a factor of 10, we reduce exp by 10 instead. This decision helps us in later steps.

//Step 3: Print the character matching that digit
	out('0' + digit);

By now this trick should be familiar. The character codes for the digits are consecutive, so the character code for 4 is four places after the code for 0, and the '0' notation gets us the character code for 0.

//Step 4: Subtract that digit's value from the number
	num = num - digit * exp;

Here’s the first place where reducing exp at Step 2 helps us, the sum is very simple.

In order to perform Step 5, we have to put the following loop around Steps 2 – 4:

	while(exp > 1)
	{
	...
	}

This is the other place that reducing exp helps us. Each time we restart the loop, we’ve reduced the length of num by one digit, and done the same to exp, so the algorithm continues to work! Also, we know that we’ve done all the digits when exp is equal to 1, hence the way the while tests is written.

Now we need to fix negative numbers – at the moment Step 1 would treat all negative numbers as being length 1! The fix is easy though.

//Step 0: For negative numbers, output a minus sign
//and make the number positive
	if(num < 0)
	{
		out('-');
		num = num * -1;
	}

We output the minus sign, then just convert the number to the positive number with the same digits!

Since that’s a bit of a jigsaw puzzle, the full function is hidden here for reference:

void T_Integer(float num, void(float c) out) =
{
//Step 0: For negative numbers, output a minus sign and make the number positive
	if(num &lt; 0)
	{
		out('-');
		num = num * -1;
	}	
//Step 1: Work out how many digits long the number is
	local float exp;
	local float digit;
	exp = 10;
	while(exp &lt;= num)
		exp = exp * 10;
	
//Step 2: Get the leading digit of the number
	while(exp &gt; 1)
	{
		exp = exp / 10;
		digit = floor(num/exp);
//Step 3: Print the character matching that digit
		out('0' + digit);
//Step 4: Subtract that digit's value from the number
		num = num - digit * exp;
	}
}

Homework

1. Modify T_Integer to print out golden numbers instead of white ones.

2. Create a version of T_integer which prints numbers in base 8 instead of base 10.

3. The biggie: Create T_Float, which outputs floating point numbers with 6 places after the decimal point. Hint: you may want to make use of T_Integer within T_Float, but do so carefully!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s