To cast or not to cast

In our attempt to make X# as compatible as possible we are supporting almost all the constructs of the VO language. One of the language constructs that gives some problems is the _CAST operation.

Take the following code:

FUNCTION start
LOCAL uValue AS USUAL
LOCAL siValue AS SHORT
uValue := 100
siValue := SHORT(_CAST, uValue)
? uValue
? siValue
WAIT
RETURN NIL

Which result is printer for siValue and uValue ?

Most of you will answer 100, and that is correct.

What if we change the example to:

FUNCTION start
LOCAL uValue AS USUAL
LOCAL siValue AS SHORT
uValue := MyFunction()
siValue := SHORT(_CAST, uValue)
 ? uValue
? siValue
WAIT
RETURN NIL

FUNCTION MyFunction
RETURN 1 - PI

What will the answer be ? (And yes, PI is a predefined constant in VO and X# with the value of 3.14159265358979323846). And please, don't cheat, don't run the example in VO. Just solve this in your head.

The answer to the second example is -2.14 for uValue and some random number for siValue.

Why ?

The reason for this is that the _CAST operator tells the compiler that you know what you are doing, and in this example that is obviously not the case.

Let me explain a little bit about USUALs and how their values are stored:
The USUAL type in VO can hold different types of values. In memory the USUAL type occupies 8 bytes. The first 4 bytes are a union that hold the value (when it is a value type) or a reference to the value (when it is a reference type). The second 4 bytes hold flags, such as the type of the USUAL.
The value -2.14 is a FLOAT and is stored as a reference type in VO. So the first 4 bytes of the USUAL contain the address of the FLOAT.
The SHORT(_CAST operation tells the compiler to take the first 2 bytes of the USUAL structure and treat its value as a SHORT. So what you will get is the first 2 bytes of the address of the FLOAT.
In fact, the reason why it works with the first example (where we are assigning a literal 100, which is stored as a LONG in the usual) is that on the intel platform the bytes are stored in Little Endian (= reversed) order. So the value 100 (0x64) is stored as 64 00 00 00 where the first 2 bytes are the lower part of the number. When you cast the USUAL to a SHORT then this only works because the intel platform uses Little Endian.
On another platform this would never work. If you want know more about "endianness" (yes that is really a word), look at https://en.wikipedia.org/wiki/Endianness.
And if the bytes in USUAL would have been stored in the oppositie order (type followed by value) then this would have never worked at all. Any _CAST on a USUAL would have returned the type and not its value.

So what is the good solution ?

In general I would say, don't use _CAST unless you really know what you are doing (and even when you think you know what you are doing, think again).

The good solution in this case is to replace

SHORT(_CAST, expression)

with

SHORT(expression)

If you try that in the examples above you will see that you will get the correct results in most situations. However it will not produce the same result for this example:

 

uValue  := 100000 
siValue := SHORT(uValue)
siValue := SHORT(_CAST, uValue)

The first operation (the conversion to SHORT) will throw an overflow error because the value does not fit in a SHORT. The second expression will assign the value -31072 to siValue (100000 is represented in HEX as 0x186A0. In memory this looks like: A0860100. The _CAST operation will read the first 2 bytes (86A0).

It is difficult for us to know if the "right" anwer here is an overflow or a completely different value. We can't know because we don't know what your code is doing.

So here are some interesting questions:

  • How should we solve this in X# and how can we be sure that your code works on any platform, not only Intel?
  • How compatible should we be?
  • Should the X# code for SHORT(_CAST from a usual with a float value also return a random number ?

Please let us know what you think about this.

 


One comment