Thursday, November 27, 2014

Pebble Watchface: Eliminating Floating Point Math

The Pebble watch hardware can't actually do floating point calculations (numbers with decimals). Those calculations are emulated in software. As a result, if you do even a single floating point operation in your Pebble app, the compiler automatically includes additional library code to be able to process them.

I was doing floating point calculations for my temperature and wind speed conversions; the size of my Pebble app was: 11531 bytes
When I changed those to be integer calculations only, the size of my app reduced to: 8955 bytes

So I got an immediate savings of 2576 bytes of space by eliminating floating point operations. (That's essentially a rounding error for a desktop computer, but it's significant for an embedded device / smart watch.)

My original conversion code:

static float getPreferedWindSpeed(float windSpeed_metersPerSecond)
{
  switch(windSpeedUnits)
  {
    case WINDSPEED_UNITS_KNOTS:
      return (windSpeed_metersPerSecond * 1.94384);
    case WINDSPEED_UNITS_MPH:
      return (windSpeed_metersPerSecond * 2.23694);
    case WINDSPEED_UNITS_KPH:
      return (windSpeed_metersPerSecond * 3.6);
  }
  return windSpeed_metersPerSecond;
}


To convert those floating point multiplications to integer calculations, I need to use integer numbers only and perform 2 calculations: first multiply the value then divide the magnitude.

So for MPH conversion, instead of * 2.23694, I'll split that into: * 223694 / 100000

It's important my first calculation is the multiplication, as doing the divide first will round out the number to 0.

My new conversion code:

static int getPreferedWindSpeed(int windSpeed_metersPerSecond)
{
  switch(windSpeedUnits)
  {
    case WINDSPEED_UNITS_KNOTS:
      return (windSpeed_metersPerSecond * 194384 / 100000);
    case WINDSPEED_UNITS_MPH:
      return (windSpeed_metersPerSecond * 223694 / 100000);
    case WINDSPEED_UNITS_KPH:
      return (windSpeed_metersPerSecond * 36 / 10);
  }
  return windSpeed_metersPerSecond;
}

A fairly trivial code change! (At least for wind speeds.) Once I did this for all calculations the size of my compiled watch face automatically went down.

One thing to keep in mind is that the maximum size of a Signed Integer is limited to 2^31 - 1 or 2147483647. We don't want to exceed that in our calculations.

Let's look at the worst case conversion which happens when getting MPH. To get the maximum wind speed we can safely calculate, we'll take the maximum size of an integer and divide that by the multiplication operation: 2147483647 / 223694 = 9600

As long as my wind speed is below 9600 meters/second, I'm going to remain within the limits of an Integer value. And considering that's faster than a spacecraft in low Earth orbit, I think I'm ok.

Say like you needed to calculate larger numbers - you can decrease the precision by an order of magnitude to get an order of magnitude increase in maximum size. For example use: 22369 / 10000, which would allow 96000 meters/second.

If you needed to do floating point division, then just multiply by the magnitude first (100000) then divide by the value (223694).

Side note, be sure to NOT include any decimals; do not do 100000.0! Otherwise, the compiler will interpret that as a float and will load the floating point library.

No comments:

Post a Comment