Complete
over 3 years ago

Not a CIQ issue. For more context, here's a link to the Bug Reports FAQ, https://forums.garmin.com/developer/connect-iq/w/wiki/5/bug-reports-faq.

Next time, consider starting a discussion. A discussion can be escalated to a bug report.

Unable to test km to mile conversion of half marathon

```

  (:distance)
  module Distance {

    function toMiles(dist) {
      var mi = dist.toDouble() * 0.6213711922d;
      return mi.toDouble();
    }

  }

I have the following Test:

// t.is() == test.assertEqualmessage
// t.ok() == test.assertMessage()
// t.pass() == test.assertMessage(true, msg)
// t.fail() == test.assertMessage(false, msg)

    (:test)
    function testDistance(logger) {

      t.is(dist.toMiles(1), 0.6213711922d, "1km is 0.6213711922 miles");

      var mi = dist.toMiles(21.0975);
      Sys.println(mi); // prints 13.109379

      var mile = 13.109379d;
      Sys.println(mile); // print 13.109379

      if (mile == mi) {
        t.pass("A mile is a mile");
      }
      else {
        t.fail("A mile isn't a mile");
      }

      t.is(dist.toMiles(21.0975), 13.109378728d, "Half marathon distance");
      t.ok(dist.toMiles(21.0975) == 13.109379d, "Half marathon distance");

      return true;
    }

This fails on Exception: ASSERTION FAILED: A mile isn't a mile    File: UnitTests

I'm kinda expecting it all to work.


The problem is because I cannot see what the test sees (as described here https://forums.garmin.com/developer/connect-iq/i/bug-reports/test-assertequalmessage-and-friends-to-show-incorrectness), so from there I'm sitting the values should be correct. Working around it by adding a toString() doesn't work, it still thinks it isn't the same value.

  • Yeah my only point was that you can't expect 13.109379d to equal dist.toMiles(21.0975), even if the latter prints out as "13.109379". It's def highly misleading.

    > What is the g modifier on the format tho?

    Sorry, "%g" is double in C (which is where these printf-style format strings originate from). I assumed that it was supposed to be part of Monkey C too, although now I see it's not documented -- my bad. Looks like it still works tho (prolly just to appease ppl like me haha).

    https://stackoverflow.com/questions/54162152/what-precisely-does-the-g-printf-specifier-mean

    "%f" does produce different output:

    System.println("miles = " + mi.format("%f")); "miles = 13.109379"
    System.println("miles = " + mi.format("%.20f")); "miles = 13.10937863262578062518"
    And the following statements all print "true", which is kinda hilarious:
            System.println(mi == 13.109378632625780625d ? "true" : "false");
            System.println(mi == 13.1093786326257806251d ? "true" : "false");
            System.println(mi == 13.10937863262578062518d ? "true" : "false");
  • The println() to six precision matches my 

    t.is(dist.toMiles(1).toString(), 0.621371d.toString(), "1km us 0.62137 miles");
    code. 

    What is the g modifier on the format tho? https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/Double.html#format-instance_function doesn't list it. In C this seems to be equal to the e modifier.

  • "6. IMHO == can be only used for Float, as Double is an Object, so you'll just need to switch mile == mi to mile.equals(mi) and probably all will work."

    This is incorrect -- "==" works fine with doubles. As noted, the actual problem is that the value of "mi" isn't what OP thinks it is.

    As a matter of fact, both Floats and Doubles are Objects in Monkey C, it's just that a Float is a "simple object" (the object just consists of the type and the value, for 6 bytes total iirc), and a Double is complex object (the object consists of a type and a pointer to the value on the heap).

  • > allow toDouble(precision) so you can tweak the precision within the compare

    You can implement your own rounding function to do this. e.g.:

        function round(val, numDecimals) {
            val = val * Math.pow(10, numDecimals);
            val = Math.round(val);
            return val / Math.pow(10, numDecimals);
        }

    Also, I noticed that the following code rounds to 4 places:

    System.println("miles = " + mi.format("%g"));
    So you really can't rely on the output of println() unless you ask for a lot of decimal places (like 20).
  • IMO the actual problem here is that System.println(<double>) doesn't print all the digits by default, but instead rounds to 6 places. This is probably because they figured nobody wants to output more than 6 decimals on a watch display.

    For example, javascript behaves differently:

    > function toMiles(dist) { return 0.6213711922 * dist; }
    undefined
    > toMiles(21.0975) == 13.109379
    false
    > toMiles(21.0975)
    13.1093787274395
    > toMiles(21.0975) == 13.1093787274395
    true
    In monkey C:

    function toMiles(dist) {
          var mi = dist.toDouble() * 0.6213711922d;
          return mi.toDouble();
    }
    // test code
    var mi = toMiles(21.0975);
    System.println("miles = " + mi.format("%.20g")); // prints "13.109378632625780625"
    System.println(mi == 13.109378632625780625d ? "true" : "false"); // prints "true"