So, Can I Get Your Number? (All About Numbers in AS3) : A Bit of History For The Uninitiated
So, Can I Get Your Number? (All About Numbers in AS3) : A Bit of History For The Uninitiated
This is exactly the same way our own numbering system works, except for the fact that our own number system can count up to ten. So in other words:
101 (binary) = to "we have one group of four + zero groups of two + one group of one (4+0+1) = 5" 101 (numbers) = to "we have one group of hundred + zero groups of ten + one group of one (100+0+1) = 101"
By continually adding binary bits in this manner we eventually arrive at the following:
11111111 = "128+64+32+16+4+2+1" = 255.
And thus we arrive at the fact that 8 bits in a byte gives us 256 possible values (computers start counting at zero). Sounds familiar doesnt it? Understanding this is also happens to be the key to understanding the mystery of bitwise operators. To count higher, we obviously have to start adding bytes together. So in this way a 32bit number can actually store 256*256*256*256 = 4294967295 values. This happens to be the exact value range of int and uint. In this way when we declare an int or unit its basically a contract with the computer that the value of the number is never going to require more than four bytes, and it allows the computer to optimally assign these bytes in memory. The difference between the int and uint is that the unit value ranges starts from 0 while the int values are split equally into negative and positive numbers to provide a more workable range of values. Its at this point that you might realise the 0xAARRGGBB hex values used
for colours map all the values of uint, and in this way all colours in flash can be represented by 32bit uint numbers. As a curious side note, the same way binary is based on a 2 counting base (or radix as its technically called), and our decimal system is based on a 10 base, hexadecimal uses a 16 counting base. So that means 0xFF = 15 groups of 16 + 15 = 255 and is a short hand method of representing the value of a byte. Again this is how we use bitwise operators to separate a colour into its constituent values. Back to practicalities Of course when you set a limit on the number of values you can represent, its conceivable you could exceed this limit especially after you start adding decimal places. Enter the venerable Number, which is 64bit based and so provides a much wider range of workable values. In theory choosing appropriately between number types should allow you to optimise code, but in the benefits have been dubious until version ten of flash player. In other languages such as the C languages, which were born from an age where every byte was crucial, you have far more number types to work with each with its own memory footprint. In fact even the concept of Number gets broken down into types such as float, double, long double. The long and short of it though is that working with numbers is just about the fastest thing a computer can do, and usually its safe to go with the number type, unless the case for int is obvious, such as looping through and array, or you are working with performance sensitive code. Sorry, but you're not my type As you start to develop as an Object Orientated programmer, you may have also thought of using the different number types to your advantage, to help create type safety within the data getting passed around. Be warned though, the results might not be what you expect. Say for instance you have a function that will break if you pass anything other than a positive whole number. At this point the light bulb illuminates and you decide to declare the function so it only takes a unit as an argument. You can now rest secure in the knowledge you function won't break - job done! Ideally for this scenario to work the compiler should throw an error when you try pass a Number or int to the function, but instead (as with primitive types) the compiler will try to auto convert the number types, and this is where the weird comes in. You can of course convert uint > int > Number to your hearts content without any ill effects, but going the other way will cause problems. If you convert a Number to an int, it will simply drop every thing after the decimal place. You could use this quick and dirty way to round the number, but it is safer to use one of the math functions to be sure you are using the correct rounding method. You can also convert a positive int to uint without any ill effects, but a negative int will actually wrap around causing a massive uint value:
01 var number:Number = 23.84; 02 var posInt:int = number; 03 var posIntMath:int = Math.round(number); 04 trace(posInt); // 23 05 trace(posIntMath); //24 06
07 number = -23.84; 08 var negInt:int = number; 09 var negIntMath:int = Math.round(number); 10 trace(posInt); // -23 11 trace(negIntMath); //-24 12 13 var uInteger:uint = negInt; 14 trace (uInteger); //4294967273 - Not groovy at all!
An existential crisis The next subtlety of type conversion you might encounter is when trying to test the existence of a number, in much the same way you might test if a variable is null before executing a piece of code. If things were consistent, you would expect declaring a number would be enough for it to convert to a true Boolean by virtue of the fact that you explicitly declared the number. How ever it is important to note that if you cast zero to a boolean it actually evaluates to false. This again goes back to the fundamental basics of computers where 0 = false and 1 = true. Not only this, but a number doesn't have a null type, but rather uses a special type for a non existent value, namely NaN (Not a number). Okay, so now we're making progress - we know that if we want to evaluate the existence of a number we should check for NaN, not null. We do this by using the isNaN() function. But the confusion doesn't end there, NaN only works with the Number type, not int or uint. As soon as you try reference a int or uint, it is automatically assigned the value of zero (remember how we mentioned the computer assigns these to memory in an optimal way?)
01 var number1:Number; 02 if (number1) trace(true); 03 else trace(false); //false 04 05 number1 = 0; 06 if (number1) trace(true); 07 else trace(false); //false - number is declared, would expect true 08 09 var number2:Number; 10 if (!isNaN(number2)) trace(true); 11 else trace(false); //false 12 13 number2 = 0; 14 if (!isNaN(number2)) trace(true); 15 else trace(false); //true - number is declared, evals as expected 16 17 var integer:int;
18 if (!isNaN(integer)) trace(true); 19 else trace(false); //true - opposite of what we expect 20 21 integer = 0; 22 if (integer) trace(true); 23 else trace(false); //false - same as number
Making a call One final subtlety to note with numbers once again comes back to memory. When you declare a variable (var a) and assign it a value, basically what you're doing is defining a reference to a particular value stored in memory. When you then duplicate this variable (var b), there are two ways the program can respond. First it can simply create a new reference to the same memory location, so if var a gets changed, var b changes along with it. The alternative is that the memory value is copied to a new location, which var b then references. This means that if you change the original variable var b will remain unchanged. More advanced languages actually allow you to define how you want you variables to react, but in Actionscript it's hardwired that primitive data types adopt the latter behaviour:
1 var a:Number = 10; 2 var b:Number = a; 3 a += 5; 4 trace(a); //15 - modified 5 trace(b); //10 - unchanged
There's a good chance you've seen something like the above before, however the implications become more obvious when you start writing your own Math functions. Because copying a number variable results in a new value being created, passing a number to a function actually creates a duplicate value. Which means in order to actually work with the result of the function, you need to reassign it to the initial variable:
1 var a:Number = 10; 2 double(a); //performs the function, and a new value in memory will be doubled, but now we have no access to it.
3 trace(a); //10 4 a = double(a); //be reassigning the value, we now have access to it. 5 trace(a) //20 6 7 function double(num:Number):Number { 8 9} return num *= 2;
That's all for now, go forth and multiply! (Okay I'll stop now).
By Peter Cardwell-Gardner