Implicit Type Conversion
I’ve talked before about explicit type conversion and used the function-call style (Type(obj)
) and the as
keyword to accomplish the task. Today, I’m going to talk about implicit type conversion and use—as implicit would imply—no operators at all!
There are a limited set of circumstances where AS3 doesn’t require you to explicitly convert types. You simply assign a value of one type to a variable of another type like so:
function foo(numberVal:Number): void { var intVal:int = numberVal; // Number -> int }
This type conversion can be done even when you’ve strongly typed, like in the above example. It does not require you to use the *
type or anything *
-typed like the fields of an Object
, Array
, or Dictionary
objects. Those strongly-typed situations are what today’s article is all about, so let’s look at them:
int | uint | Number | Boolean | |
---|---|---|---|---|
int=… | YES | YES | YES | NO (error) |
uint=… | YES | YES | YES | NO (error) |
Number=… | YES | YES | YES | NO (error) |
Boolean=… | YES (warning) | YES (warning) | YES (warning) | YES |
As you can see, type conversion between the basic types is often permissible. The exceptions are that you can’t implicitly convert a Boolean
to an int
, uint
, or Number
as you get a compiler error in MXMLC 4.1:
Error: Implicit coercion of a value of type Boolean to an unrelated type int. Error: Implicit coercion of a value of type Boolean to an unrelated type uint. Error: Implicit coercion of a value of type Boolean to an unrelated type Number.
You can, however, do the reverse and implicitly convert an int
, uint
, or Number
to a Boolean
, but you get a compiler warning:
Warning: int used where a Boolean value was expected. The expression will be type coerced to Boolean. Warning: uint used where a Boolean value was expected. The expression will be type coerced to Boolean. Warning: Number used where a Boolean value was expected. The expression will be type coerced to Boolean.
Ignoring those warnings, I made up a little performance test to check the speed of these implicit conversions:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class ImplicitTypeConversion extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } public function ImplicitTypeConversion() { __logger.autoSize = TextFieldAutoSize.LEFT; addChild(__logger); const REPS:int = 100000000; var i:int; var beforeTime:int; var afterTime:int; var loopTime:int; var intVal:int = 1; var uintVal:uint = 1; var numberVal:Number = 1; var booleanVal:Boolean = true; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { } afterTime = getTimer(); loopTime = afterTime - beforeTime; log("int=..."); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = intVal; } afterTime = getTimer(); log("\tint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = uintVal; } afterTime = getTimer(); log("\tuint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { intVal = numberVal; } afterTime = getTimer(); log("\tNumber: " + ((afterTime-beforeTime)-loopTime)); log("\tBoolean: n/a"); log("uint=..."); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = intVal; } afterTime = getTimer(); log("\tint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = uintVal; } afterTime = getTimer(); log("\tuint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { uintVal = numberVal; } afterTime = getTimer(); log("\tNumber: " + ((afterTime-beforeTime)-loopTime)); log("\tBoolean: n/a"); log("Number=..."); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = intVal; } afterTime = getTimer(); log("\tint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = uintVal; } afterTime = getTimer(); log("\tuint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { numberVal = numberVal; } afterTime = getTimer(); log("\tNumber: " + ((afterTime-beforeTime)-loopTime)); log("\tBoolean: n/a"); log("Boolean=..."); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = intVal; } afterTime = getTimer(); log("\tint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = uintVal; } afterTime = getTimer(); log("\tuint: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = numberVal; } afterTime = getTimer(); log("\tnumber: " + ((afterTime-beforeTime)-loopTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { booleanVal = booleanVal; } afterTime = getTimer(); log("\tBoolean: " + ((afterTime-beforeTime)-loopTime)); } } }
One new feature of this performance test is that I’m subtracting out the time an empty loop takes. This is especially important as the actual body of the loop is so light that the time spent just doing the loop is a very significant chunk of the total. With the overhead gone, we can see much more clearly how much time is actually spent doing the type conversion. The only downside is that we can get negative times when the type conversion is nearly instantaneous. I’ll just list those as zeroes in the performance results below, but in actuality they were sometimes slightly negative or slightly positive. The following is for Flash Player 10.1.102.64 running on a 2.4 Ghz Intel Core i5 with Mac OS X 10.6:
int | uint | Number | Boolean | |
---|---|---|---|---|
int=… | 0 | 0 | 265 | n/a |
uint=… | 0 | 0 | 264 | n/a |
Number=… | 0 | 101 | 0 | n/a |
Boolean=… | 24 | 26 | 205 | 0 |
There were many zeroes in the above test, again indicating that the type conversion is nearly instantaneous. For some conversions though, this is clearly not the case:
Number
toint
oruint
is very costly as the operation is inherently expensive. Conversion toBoolean
is faster as there are only two possible values.- While converting
int
toNumber
is fast, convertinguint
toNumber
is mysteriously not fast at all. - Converting
int
anduint
toBoolean
is not quite free. It takes a little bit of time, but it’s quite unlikely to be the source of any real-world problems.
The above performance results table should serve as a good reference. Beware any conversion from Number
in performance-critical code. Since it takes no typing at all to do the conversion, this could be a silent performance killer.
#1 by Henke37 on November 15th, 2010 ·
Looks like they forgot to optimize a case with uint again.
#2 by jwopitz on November 16th, 2010 ·
Or maybe they are doing some RT checks to ensure that the value is positive. Or they are converting to positive regarldess. I wonder what the bytecode would look like in this case.
#3 by skyboy on November 17th, 2010 ·
Probably something along the lines of:
$convert can be one of the following:
convert_string
convert_int
convert_uint
convert_double // Number
convert_bool
convert_object
coerce_any
coerce_string
coerce
The last one being the generic for all classes. String has two because
convert
works slightly differently with null/undefined thancoerce
does.#4 by jackson on November 17th, 2010 ·
skyboy is correct. For example:
Compiles to:
2, 3, and 4 are the operative instructions. This happens regardless of what the parameter type is. So there’s no special “convert_n_to_i” instruction when the input is a
Number
, for example.#5 by jpauclair on November 26th, 2010 ·
if you look at the “number to int” more closely.
In my article about flash asm: http://jpauclair.net/2010/03/15/flash-asm/
you will see at line 28 in the assembler:
30:convert_i
29
@81 csop AVMCORE_integer_d_sse2 (@80)
This is what slow down the process. it need to call a special SSE instruction to be able to convert from float to int.
#6 by jackson on November 28th, 2010 ·
Float-to-int is an inherently expensive operation, but it’s good to know that at least the Player is using SSE where available.