Acknowledged
CIQQA-3581

Casting Float to Number does not yield Number

This code:

var myFloat = 5.0;
var myNumber = myFloat as Number;
var mod = myNumber % 1;

Compiles without warnings, but crashes at runtime with:

Error: Unhandled Exception
Exception: UnexpectedTypeException: Expected Number/Long, given Number/Float

As far as I can tell:

  • this behavior is undocumented
  • the types "Number/Long" and "Number/Float" are undocumented
  • the effects of casting numeric types are undocumented

Consequently I'm not sure how to characterize this bug. If it's expected, this is a documentation bug. If it's not expected, this is a runtime and/or compiler bug.

I read the relevant section on "type casting" in the SDK docs. I've reproduced the totality of the documentation on type casting here:

The as keyword can also be used in an expression to type cast a value to another type. This can be useful if the type is not clear to the type system.

This, unfortunately, does not help very much.

---

Tested on Descent G1 & G2 simulators

SDK 8.3.0

Parents
  • > Exception: UnexpectedTypeException: Expected Number/Long, given Number/Float

    > the types "Number/Long" and "Number/Float" are undocumented

    Those aren't distinct types, it's just typically bad CIQ error messages.

    What that error means in the context of "x % y" is that:

    - "Expected Number/Long": each of the operands x and y is expected to be Number or Long 

    - "given Number/Float": one operand was Number and the other operand was Float

    Yes, I realize this is vague and lacking in self-consistency - for example, the "/" has 2 different meanings in the same message. In "Expected A/B", the "/" means "or", but in "given C/D", the "/" means "and".

    But you can validate what I'm saying with a few examples:

    var x = (1.0 as Number);
    var y = (2.0 as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given Float
    // ("expected either Number or Long, given Float")
    --

    var x = (1.0 as Number);
    var y = ("hello" as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given Float/String
    // ("expected either Number or Long, given Float and String")
    --
    var x = ("goodbye" as Number);
    var y = ("hello" as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given String
    // ("expected either Number or Long, given String")
Comment
  • > Exception: UnexpectedTypeException: Expected Number/Long, given Number/Float

    > the types "Number/Long" and "Number/Float" are undocumented

    Those aren't distinct types, it's just typically bad CIQ error messages.

    What that error means in the context of "x % y" is that:

    - "Expected Number/Long": each of the operands x and y is expected to be Number or Long 

    - "given Number/Float": one operand was Number and the other operand was Float

    Yes, I realize this is vague and lacking in self-consistency - for example, the "/" has 2 different meanings in the same message. In "Expected A/B", the "/" means "or", but in "given C/D", the "/" means "and".

    But you can validate what I'm saying with a few examples:

    var x = (1.0 as Number);
    var y = (2.0 as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given Float
    // ("expected either Number or Long, given Float")
    --

    var x = (1.0 as Number);
    var y = ("hello" as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given Float/String
    // ("expected either Number or Long, given Float and String")
    --
    var x = ("goodbye" as Number);
    var y = ("hello" as Number);
    var z = x % y; // Exception: UnexpectedTypeException: Expected Number/Long, given String
    // ("expected either Number or Long, given String")
Children
  • Thank you for very much for taking the time to clarify all of this.

    Therefore, the only purpose of a type cast is to override the type checker's idea of the type of a variable/value. A cast can *never* change the actual run-time type (or value) of a variable.

    This is helpful. Considering this code:

    // Number is a 32-bit signed integer
    var myNumber as Number = 0;

    function setMyNumber(value) {
        myNumber = value;
    }

    function test() {
        setMyNumber(5.12);
        System.println(myNumber.format("%.2f"));
    }

    According to the docs, this code should not compile:

    Once a type has been bound to a value, the compiler will only allow values of that type to be assigned.

    Since it does compile, this implies type coercion; but thanks to your explanation, I now understand that the docs are wrong.

    Prior to your explanation, I expected a numeric conversion to occur and the output to be "5.0". In fact the output is "5.12". I see now that this is just normal duck typing behavior. The presence of a casting operator made me think that a numeric conversion was performed, but I suppose this is on me as the docs do state that there are no primitive types, only objects. Intuitively, I would expect "as Float" to call ".toFloat()" on numeric types; this would follow the least surprise principle which Moron C claims is a priority. In any case, it's now clear why casting effects aren't documented - there aren't any effects.

    Btw,  this is why type casting is so dangerous in Monkey C, and I why I always suggest avoiding it unless absolutely necessary.

    I completely agree with you and will now eliminate casting from my program.

    Those aren't distinct types, it's just typically bad CIQ error messages. [...] the "/" has 2 different meanings in the same message. In "Expected A/B", the "/" means "or", but in "given C/D", the "/" means "and".

    Incredible. Thank you for explaining this. It's clear to me now.

    var x = ("goodbye" as Number);
    var y = ("hello" as Number);

    The fact that this compiles drives home your point about casting even more.

    Since you seem to be a better language reference than the official docs, I have a couple more questions:

    1. How is arithmetic handled? Underflow, overflow, promotion etc. are all undocumented as far as I've seen.
    2. It seems to be impossible to specify Double or Long literals. Instead you have to write "5.0.toDouble()" or "5.toLong()". Correct?
    3. While numeric types are objects, there also seems to be no operator overloading. Correct?

    Thank you again, I think you have saved me a lot of confusion.