Large numbers (like unix epoch) calculation inaccurate

When using a large number such as Time.now().value() (which is Unix epoch, looks like 1547505511), I notice that the system is unable to correctly perform simple math calculations, specifically additions and subtractions. This applies to both the simulator and the actual devices.
If adding 1, the result is correct. If adding 253, the result may be +252 or +256.
As a result, the following code doesn't always return the correct minute.
var nowSecondClock = new Time.Moment(Time.now().value() + userTimezoneOffset * 3600);
var secondClock = Calendar.utcInfo(nowSecondClock, Time.FORMAT_MEDIUM);
var secondClockStrArray = formatTime(secondClock.hour,secondClock.min); // -> This would sometime return a minute value that is +1 or -1 from the actual
//var secondClockMin = clockTime.min + (userTimezoneOffset-userTimezoneOffset.toNumber())*60; // -> This gives the right result but unduly requires more complex calculations


I don't know if anyone else noticed this.
  • Force the conversion to double using toDouble? Use 3600.0d instead of an integer?
  • TL;DR your TZ offset is obviously expressed as float hours, which is why your code doesn't work as expected.

    Floating point arithmetic is inexact and will always be inexact, no matter what platform you use. http://effbot.org/pyfaq/why-are-floa...inaccurate.htm

    Notice that every standard library/SDK, including Connect IQ, expresses TZ offset in integer minutes or seconds, and there's a very good reason for that.

    The quick answer is don't use floats when you need exact answers, like when you are doing stuff with time....

    If you are curious, banks don't use floats either, for this very reason -- they use fixed-point. (e.g. $123.45 could be represented as 12345 (integer). In practice they would use more decimal points tho, to calculate interest/etc.

    The long answer....


    It looks to me from your example (which is missing a bit of context), that userTimezoneoffset is the TZ offset in hours, as a float. I assume it's a float because:
    • Of the code that reads
      Time.now().value() + userTimezoneOffset * 3600
      and
      clockTime.min + userTimezoneOffset-userTimezoneOffset.toNumber())*60
      (obviously this 2nd one only makes sense if userTimezoneOffset is a float)
    • Some timezone offsets are multiples of 0.5 hours
    Unfortunately, doing arithmetic with floats will not give you what you want in this case, no matter what language/platform you use, now and in the future. Why? Because most decimal numbers can't be represented exactly in binary floating-point. So when you do arithmetic with floats, you won't get an exact result in general. The purpose of floats is to do efficient calculations with decimal fractions that don't need to have exact results.

    http://effbot.org/pyfaq/why-are-floa...inaccurate.htm

    So yeah, I can write a test case where everything fails, by using a float TZ offset:
    var now = Time.now().value();
    System.println("now = " + now);
    var tzOffsetFloatHours;
    var secondsPerHour = 3600;
    System.println(" secondsPerHour = " + secondsPerHour);

    // yes I know this isn't the real TZ offset range....
    for (tzOffsetFloatHours = 0; tzOffsetFloatHours <= 12.0; tzOffsetFloatHours += 0.5)
    {
    System.println(" tzOffsetFloatHours = " + tzOffsetFloatHours);
    System.println(" tzOffsetFloatHours*secondsPerHour = " + (tzOffsetFloatHours*secondsPerHour));

    // Wrong answer most of the time because float arithmetic is inexact
    System.println(" tzOffsetFloatHours*secondsPerHour + now = " + ((tzOffsetFloatHours*secondsPerHour) + now));
    System.println("");
    }


    Now here's my test case where everything works, because I only used integers.
    // run this after the previous test case without reinitializing now so you can compare numbers
    System.println("now = " + now);
    var tzOffsetIntMinutes;
    var secondsPerMinute = 60;
    System.println(" secondsPerMinute= " + secondsPerMinute);

    // yes I know this isn't the real TZ offset range....
    for (tzOffsetIntMinutes = 0; tzOffsetIntMinutes <= 720; tzOffsetIntMinutes += 30)
    {
    System.println(" tzOffsetIntMinutes = " + tzOffsetIntMinutes);
    System.println(" tzOffsetIntMinutes*secondsPerMinute = " + (tzOffsetIntMinutes*secondsPerMinute));

    // gives the correct answer 100% of the time
    System.println(" tzOffsetIntMinutes*secondsPerMinute + now = " + ((tzOffsetIntMinutes*secondsPerMinute) + now));
    System.println("");
    }


    ---

    In the future, it would help if your test code matched the description of the problem tho and/or had full context so we wouldn't have to make an educated
    guess that timezoneOffset is a float (in this case). It would make it easier for others to help you.

    If we could just run the exact same code and see the exact same results you are describing, it would be very helpful.

    I only say this because in the past I've done the same thing too (as recently as a couple of days ago), and it made it harder for me to get help.

    When using a large number ... I notice that the system is unable to correctly perform simple math calculations, specifically additions and subtractions.. If adding 1, the result is correct. If adding 253, the result may be +252 or +256.

    ^ No offense, but your test case looks nothing like what you said there.

    For example, I was unable to get the following code to fail. (The reason of course is that there's actually nothing wrong with adding integers.)
    var now = Time.now().value();
    System.println("now = " + now);
    System.println("now + 253 = " + (now+253)); // This works
  • Hey FlowState,
    Thanks a lot for your help! And for your recommendation on posting coherent code with full context!
    You're absolutely right, I didn't post a very coherent post. I saw the issue on two different instances, one with the +253/+256, and one with timezone. I actually had no idea this could have been the problem: I'm using integers for time and also fixed floating for other things like lat/lng, so I didn't expect this would be a problem because I didn't remember the timezone variable was a float.
    So kudos to you for figuring it out with my incomplete code!
    Thanks a bunch!
    I'll test it tomorrow.