https://blog.jgc.org/2022/06/the-mysterious-behaviour-of-flying.html jgc.org blog 2022-06-09 The mysterious behaviour of the Flying Tiger Countdown Clock (and the bug that causes it) Flying Tiger is a Danish "variety shop". It sells all manner of inexpensive trinkets, utensils and runs through stock quickly so there's always something seasonal. I came across a simple clock that shows the number of days remaining before some event (your birthday, the next solar eclipse, ..., whatever you choose). [tiger2] It works. Sometimes, but other times it gets the "days remaining" count completely wrong. It can be wildly too large, or wildly too small. I couldn't resist trying to reverse engineer the algorithm used and the bugs that are in the implementation. Let's start with some combinations of the current date and the deadline being counted down to that work fine. Date Deadline Delta in Days Displayed Error 2020-01-01 2021-01-01 366 366 0 2020-01-01 2022-01-01 731 731 0 2020-01-01 2023-01-01 1096 1096 0 2020-01-01 2020-01-02 1 1 0 2020-01-01 2020-02-01 31 31 0 2020-01-01 2020-01-01 0 0 0 2019-01-01 2020-01-01 365 365 0 Now take a look at when things go wrong. Same Year But here are some that go wildly wrong. My best guess at understanding why is based on assuming (after a lot of observation) that the programmer has a number of special cases within their code. Firstly, if the month and year are the same in the date and deadline then the number of days is correct (and I assume they just subtract the deadline from the date). This special case actually made tracking down what was going on for other combinations more complex (and later I realized that special case wasn't needed). But here are pairs of dates in the same year. Date Deadline Delta in Days Displayed Error 2020-01-01 2020-09-01 244 244 0 2020-01-01 2020-10-01 274 18 -256 2022-09-10 2022-10-27 47 65327 65280 2022-10-10 2022-11-11 32 32 0 For the moment, ignore the third line as it involves more reasoning about what's happening. I believe the algorithm used to calculate the days is as follows (when both dates are in the same year). Here I show the working for the second line above. Notice how the result is correct with dates before and after September. (first and last lines above) We'll see why that matters soon. date_as_day_of_year = day_of_year(2020-01-01) deadline_as_day_of_year = day_of_year(2020-10-01) delta = deadline_as_day_of_year - date_as_day_of_year I am assuming that the clock has a day_of_year function that converts a date to a sequential day number (e.g. January 1 is day 0, February 25 is 55, etc.). It appears that the return value of that function is mistakenly turned into an unsigned 8-bit integer: date_as_day_of_year = day_of_year(2020-01-01) // 0 deadline_as_day_of_year = day_of_year(2020-10-01) // 18 (not 274) delta = deadline_as_day_of_year - date_as_day_of_year // 18 The reason September is important is that the 255th day of the year is September 12 (September 11 in leap years) and so calculations will go wrong when they cross September. Note that for the fourth line above the calculation works even though the returned values have been turned into unsigned 8-bit integers. [tiger1] What about the strange number 65327? I think that comes about when the clock tries to display a negative integer. The calculation is: date_as_day_of_year = day_of_year(2020-09-10) // 253 deadline_as_day_of_year = day_of_year(2020-10-27) // 44 delta = deadline_as_day_of_year - date_as_day_of_year // -209 If delta is an unsigned 16-bit integer then -209 comes out to be 65327. Alternately, it's signed but the output function (sprintf?) assumes it's unsigned. So, it looks like the clock uses the day of the year for calculations and has type errors that cause weird output. What about spanning more than one year? Crossing New Year When crossing the new e the algorithm above won't work but something similar will. First calculate the remaining days in the current year and then the additional days in the subsequent year to reach the deadline. Add them together. Take an actual example from the clock (one that works!) Date Deadline Delta in Days Displayed Error 2020-09-30 2021-01-02 94 94 0 Here's pseudocode for that: days_in_current_year = days_in_year(2020-09-30) date_as_day_of_year = day_of_year(2020-09-30) deadline_as_day_of_year = day_of_year(2021-01-02) delta = (days_in_current_year - date_as_day_of_year) + deadline_as_day_of_year (Note that I've ignored the case where there are multiple intervening years. The clock just adds the number of days in each additional year and does that part correctly.) That algorithm appears to be what the clock is using and here's a long dump of test dates and deadlines. Look at the lines with errors. They all involve a date or deadline after September. Did the programmer make the same 8-bit unsigned int error twice? Date Deadline Delta in Days Displayed Error 2020-08-30 2021-01-02 125 125 0 2020-08-30 2021-02-02 156 156 0 2020-08-30 2021-03-02 184 184 0 2020-08-30 2021-04-02 215 215 0 2020-08-30 2021-05-02 245 245 0 2020-08-30 2021-06-02 276 276 0 2020-08-30 2021-07-02 306 306 0 2020-08-30 2021-08-02 337 337 0 2020-08-30 2021-09-02 368 368 0 2020-08-30 2021-10-02 398 142 -256 2020-08-30 2021-11-02 429 173 -256 2020-08-30 2021-12-02 459 203 -256 2020-09-30 2021-01-02 94 94 0 2020-09-30 2021-02-02 125 125 0 2020-09-30 2021-03-02 153 153 0 2020-09-30 2021-04-02 184 184 0 2020-09-30 2021-05-02 214 214 0 2020-09-30 2021-06-02 245 245 0 2020-09-30 2021-07-02 275 275 0 2020-09-30 2021-08-02 306 306 0 2020-09-30 2021-09-02 337 337 0 2020-09-30 2021-10-02 367 111 -256 2020-09-30 2021-11-02 398 142 -256 2020-09-30 2021-12-02 428 172 -256 2020-10-30 2021-01-02 64 320 256 2020-10-30 2021-02-02 95 351 256 2020-10-30 2021-03-02 123 379 256 2020-10-30 2021-04-02 154 410 256 2020-10-30 2021-05-02 184 440 256 2020-10-30 2021-06-02 215 471 256 2020-10-30 2021-07-02 245 501 256 2020-10-30 2021-08-02 276 532 256 2020-10-30 2021-09-02 307 563 256 2020-10-30 2021-10-02 337 337 0 2020-10-30 2021-11-02 368 368 0 2020-10-30 2021-12-02 398 398 0 Initially, I thought the programmer must have made the same error, but there was something odd. If it were true then the errors would be non-zero for part of September (after September 11 or 12) as well as October and beyond. But looking at the example I have above of September 30, the answer was correct. And I suspect that there was more likely to be one bug causing the observed behaviour in all cases than multiple odd places where changing integer size happened. The Bug So, what could cause the value of day_of_year to be wrong for October, November and December, and, specifically, be off by 256? Actually, I think it's pretty easy to write that function incorrectly in a subtle way. Here's an implementation of day_of_year that is correct right up until September 30 and then goes wrong in exactly the way the clock fails. (I've ignored leap years for clarity, and because the clock handles leap years just fine). #include "stdio.h" #include "stdint.h" uint8_t days[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; uint16_t day_of_year(int month, int day) { uint8_t count = 0; for (int i = 1; i < month; i++) { count += days[i-1]; } return count + day; } int main() { int m = 10; int d = 5; printf("%02d-%02d %d\n", m, d, day_of_year(m, d)); return 0; } Can you see the error? count only overflows after September. Naturally, there could be other ways to implement this function, including pre-computing the days in each month. But I think this is likely the source of the error that causes the clock to fail oddly. So, Flying Tiger, where's the source code, and can I fix it? at June 09, 2022 Email ThisBlogThis!Share to TwitterShare to FacebookShare to Pinterest 1 comment: [blo] Michael D said... Send it to a MAMEdev for decapping, dumping and emulation in MAME ;-) 11:19 AM [icon_delet] Post a Comment Older Post Home Subscribe to: Post Comments (Atom) Popular Posts * [tiger2] The mysterious behaviour of the Flying Tiger Countdown Clock (and the bug that causes it) Flying Tiger is a Danish "variety shop". It sells all manner of inexpensive trinkets, utensils and runs through stock quickly so ... * [Picture] Your last name contains invalid characters My last name is "Graham-Cumming". But here's a typical form response when I enter it: Does the web site have any idea how r... * How to write a successful blog post First, a quick clarification of 'successful'. In this instance, I mean a blog post that receives a large number of page views. For ... * [Screen] Setting up and running the MAME emulation of the Minitel 2 In a previous post I showed how I modified a Minitel 2 to run arbitrary firmware . The firmware I used came from a chap who has played aroun... * Making an old USB printer support Apple AirPrint using a Raspberry Pi There are longer tutorials on how to connect a USB printer to a Raspberry Pi and make it accessible via AirPrint but here's the minimal ... * [IMG_2772] How to forcibly eject a CD/DVD from a MacBook Air USB SuperDrive So you've got a DVD stuck in the external USB SuperDrive that connects to the MacBook Air? And you've tried hitting the magic Eject... * [camera] Ripping old mini DV video tapes on a Mac I have an old Panasonic PV-GS500 camcorder and a bunch of videotapes. The only way to preserve digital media is to keep moving it to new ver... * [AVvXsEgUs0] Using a Minitel 1B as a serial terminal So you've got your hands on a late 1980s Minitel 1B (or something slightly later) and then you realize that the entire Minitel network s... * What the "Silicon Valley" Easter Egg code does and how In the TV series Silicon Valley there was apparently a snippet of code presented that is part of a compression algorithm. The code (or at ... * List of English words that can be spelt using elements from the periodic table For example, alcoholic = "Al C O H O Li C". The longest word appears to be nonrepresentationalism. ach ago agy alb alf alk aln... Blog Archive * V 2022 (7) + V June (1) o The mysterious behaviour of the Flying Tiger Count... + > April (2) + > March (4) * > 2021 (6) + > November (1) + > July (1) + > May (2) + > April (1) + > January (1) * > 2020 (2) + > June (2) * > 2018 (2) + > December (2) * > 2017 (4) + > May (1) + > April (3) * > 2016 (11) + > November (1) + > September (1) + > July (2) + > June (1) + > May (3) + > April (1) + > March (2) * > 2015 (11) + > November (1) + > July (2) + > May (1) + > April (5) + > March (1) + > January (1) * > 2014 (4) + > November (2) + > July (1) + > June (1) * > 2013 (39) + > September (4) + > August (1) + > July (4) + > June (2) + > May (5) + > April (12) + > March (3) + > February (2) + > January (6) * > 2012 (77) + > December (3) + > November (6) + > October (6) + > September (7) + > August (6) + > July (6) + > June (8) + > May (4) + > April (9) + > March (7) + > February (9) + > January (6) * > 2011 (79) + > December (5) + > November (7) + > October (2) + > September (5) + > August (2) + > July (8) + > June (10) + > May (6) + > April (9) + > March (6) + > February (12) + > January (7) * > 2010 (95) + > December (13) + > November (9) + > October (16) + > September (19) + > August (2) + > July (9) + > June (12) + > May (1) + > April (1) + > March (4) + > February (2) + > January (7) * > 2009 (31) + > December (1) + > November (8) + > October (3) + > September (6) + > August (9) + > June (1) + > May (1) + > January (2) * > 2008 (19) + > December (2) + > September (1) + > June (3) + > May (3) + > March (2) + > February (6) + > January (2) * > 2007 (33) + > December (1) + > November (3) + > October (5) + > August (2) + > July (2) + > June (4) + > May (3) + > April (1) + > March (6) + > February (4) + > January (2) * > 2006 (25) + > December (2) + > November (2) + > October (3) + > September (5) + > July (1) + > June (1) + > April (6) + > February (1) + > January (4) * > 2005 (3) + > December (1) + > November (2) Copyright (c) 1999-2022 John Graham-Cumming. Awesome Inc. theme. Powered by Blogger.