Supported Data Types in Communication: Number vs Long, Float vs Double

According to https://developer.garmin.com/connect-iq/core-topics/mobile-sdk-for-android/ : 

Java long, Long => Monkey C Integer*, Long (If the value of the long is small enough to be represented as an integer, it will be converted to save space.)

Java double, Double => Monkey C Float, Double (If the value of the double is within 5 matching significant fractional digits of the nearest float, it will be converted to a float to save space.)

*) see also: https://forums.garmin.com/developer/connect-iq/i/bug-reports/bug-supported-data-types-in-mobile-sdk-integer-instead-of-number 

So what does this mean on the receiver's side?

Let's say in Java I'm sending:

List<Object> message = new ArrayList<>();
message.add(1L);
message.add(1d);
List<Long> longs = new ArrayList<>();
longs.add(1L);
longs.add(98765432109876L); // Note: greater than Integer.MAX_VALUE: 2147483647
longs.add(3);
message.add(longs);

and in Monkey C I'm reading:

var data as [Long, Double, Array<Long>]?;

function onPhone(msg as Communications.PhoneAppMessage) as Void {
    data = msg.data as [Long, Double, Array<Long>];
    var isItLong = data[0];
    var isItDouble = data[1];
    var whatsInThisArray = data[2];
}

How should I treat in Monkey C the values that in the Java side are long or double? Should they be considered Number or Long, Float or Double respectively? Does it mean that I should check each one of them with instanceof?

Note that the above question intentionally doesn't mention type checker, because as far as I understand the problem is deeper than that, but when using strict type checker, then it's more obvious.

  • Note that the above question intentionally doesn't mention type checker

    Actually it does (implicitly), as you have a type declaration here:

    var data as [Long, Double, Array<Long>]?;

    And a type cast here:

    data = msg.data as [Long, Double, Array<Long>];

    If the type checker was disabled (or nonexistent), neither the declaration nor the cast would have any effect at all.

    Yes, I realize the case and declaration could also serve as documentation to the reader about what the coder thinks the types will be.

    How should I treat in Monkey C the values that in the Java side are long or double? Should they be considered Number or Long, Float or Double respectively?

    You could consider them as Numeric (which is Number or Long or Float or Double). This *should* cover any worries about supplying the type checker with an incorrect type for data

    e.g.

    var data as [Numeric, Numeric, Array<Numeric>]?;

    or even better, if you prefer to preserve the distinction between integers and floating-point values:

    var data as [Number or Long, Float or Double, Array<Number or Long>]?;

    Even though you said you intentionally didn’t mention the type checker, I am just using type declarations in the same way that you did. You can consider them to be literal type declarations, or a recommendation for you to think of data as one of those types in your head.

    Does it mean that I should check each one of them with instanceof?

    Not to state the obvious: the answer could be yes, but only if it makes a difference in the code that uses the values.

    For example, if you chose the “Number or Long, Float or Double” solution, then you would only need instanceof if you are calling a function or doing an operation which would somehow have a different effect when it’s given a Number instead of Long (or vice versa), or a Float vs Double (or vice versa). In that case, you could use instanceor and convert the value at runtime to the desired type (e.g. you could call Float.toDouble()), or you could do whatever operation needs to be done for that specific type.

    Or you could just skip the instanceof check and unconditionally convert Float/Double to Double and Number/Long to Long, for each value. 

    But again, that’s only under the assumption that the distinction between Float and Double, and the distinction between Number and Long, are actually important for your code.

    Seems to me that in most cases, Number and Long would be interchangeable, as would Float and Double, especially if you are reading values from variables of that type, as opposed to assigning values to those variables.

    It might help if you would show an example of what you would actually do with the values, and how Number vs Long or Float vs Double would actually affect your intended use case.

  • Also, as per my comment on the bug report:

    - you can use “Integer” instead of “Number or Long”

    - you can use “Decimal” instead of “Float or Double”

    Note that Integer and Decimal have existed since CIQ 1.0.0, so they predate type checking. They should also be usable with instanceof, at runtime, if I’m not mistaken.

    e.g.

    var data as [Integer, Decimal, Array<Integer>]?;

    I also think the existence of the Integer and Decimal types adds weight to my point that at least in some cases, Number/Long are interchangeable and Float/Double are interchangeable. (Those types are clearly meant for those cases)

  • Yeah, I guess you're right, instanceof is not very useful here, what I should do instead is to use toLong() and toDouble() on whatever I got, so from that point I (and the compiler :) know what types are they.

    However I think there's something I haven't added to the question, but bothers me: I thought that sending a List<Integer> from the phone and receiving it as Array<Number> is an optimized way to pass a big amount of data. I hoped that it's sending an array of int-s. Or even an array of Integer-s would be efficient. However based on the "optimization" the communication SDK does it seem to me that it can't be right, because for each and every item in the list it also has to send the type. So I imagine that in case of List<Long> instead of sending 8 bytes for each item, it must send 9. This might be still worth is a good percentage of the items is actually sent as an Integer (4+1 bytes instead of 8+1)

    But then the next thought was: wait a minute, then even when I send List<Integer> it's sending 5 bytes instead of 4 for each element?! If that's the case, then it's bad! It's an anti-optimization.

    I guess there's no easy way to check this, but I would be surprised if the implementation was smart enough to spare the +1 byte in case it's "not needed". Of course even that is not easy to know, because the api is basically sending a serializable Object to the watch, so from it's point of view it can be: [Number, Long, String, Char, ...] and it needs to pass the type for each element...

  • Yeah, I guess you're right, instanceof is not very useful here, what I should do instead is to use toLong() and toDouble() on whatever I got, so from that point I (and the compiler :) know what types are they.

    But again, it all depends on whether the distinction between Number/Long and between Float/Double actually matters to your code. If not, you should be able to use (or assume) the Integer and Decimal types.

    then even when I send List<Integer> it's sending 5 bytes instead of 4 for each element?!
    it needs to pass the type for each element...

    I can’t speak to how the data is *sent* but certainly the data which is *received* will have 1 byte for the type of element, as would be the case for any Monkey C value.

    I don’t see anywhere in the docs that specifies the underlying implementation of sending messages from the phone app to the CIQ device, but let’s say JSON is used, for the sake of argument.

    For example, if JSON was used, then the type of every element could be inferred from its value (as with JSON in general). And the conversion rules listed in the docs could still apply. Ofc JSON would be very wasteful compared in terms of data size, as opposed to say, a binary format where the type of each element is explicitly specified, so you would have even more reason for concern if JSON was used, in terms of efficiency.

  • However based on the "optimization" the communication SDK does it seem to me that it can't be right, because for each and every item in the list it also has to send the type. So I imagine that in case of List<Long> instead of sending 8 bytes for each item, it must send 9. This might be still worth is a good percentage of the items is actually sent as an Integer (4+1 bytes instead of 8+1)

    But then the next thought was: wait a minute, then even when I send List<Integer> it's sending 5 bytes instead of 4 for each element?! If that's the case, then it's bad! It's an anti-optimization.

    If by “optimization” you mean the numeric conversion rules you mentioned, I don’t think what you’re saying necessarily follows. I mean that “optimization” is not the only possible reason for having to send the type (and not even the primary reason), and there are some cases where “optimization” could happen but the type doesn’t need to be (explicitly) sent. I also mean that it’s possible for Garmin to send data as efficiently as possible, regardless of the conversion rules.

    - Again, if data was sent as json (or a similar text format), the general types (Integer, Decimal, Boolean, String) could be inferred from their values, and the conversion rules could be applied on the *receiving* end. 

    - If data was sent as some sort of binary format, it’s possible for an array to contain a mixture of data types (say strings and numeric values), so there needs to be *some* sort of type information in the binary format

    - even if data was sent as some sort of binary format, and regardless of whether “optimization” is performed at the sending side or the receiving side, it would still be possible for Garmin to send array data as efficiently as possible, if they wanted to. e.g. For a list/array, allow a type to be specified that’s either “mixed” or a fixed type: Float, Double, Number, Long, String, …. If the type is mixed, then every element has to have explicit type data. If the type is fixed, then all elements are the same type, and type information can be omitted for individual elements. I have to admit this possibility is very unlikely as it would complicate the implementation. I don’t think they would go to all this trouble just to save a handful of bytes in the data

    TL;DR I think “optimization” (the rules for how numerical data eventually shows up on the receiving end) is kind of a red herring here. I can see it being done either on the sending side or the receiving side, given the specific rules that are mentioned. Again, using JSON as an example, all numeric values could be sent without any explicit type information (floating-point types could be distinguished from integer types by always using a decimal point in the former case, and never using a decimal point in the latter) — the receiving side could apply the rules mentioned in the docs very simply:

    - in the case of integer data, always select the smallest type that will hold the value (Number or Long)

    - for floating-point data, select the type that fulfills the following rule: “If the value of the double [or float] is within 5 matching significant fractional digits of the nearest float, it will be converted to a float to save space.” (if the original value was a float, then the same rule would surely apply in all cases.)