user84634: I need to do some math with vars from an attitude sensor (acclerometers and gyroscopes) on an esp8266. int32_t math with those vars does not have enough range and the float math operations are abyssmally slow.
I'm hoping to use a fixed-point format with 24 bits on the left of the decimal point and 8 bits on the right side.  So a number would be stored in a int32_t as 0xffffff.ff.  A typical operation would be a multiply like ((i64) x * y) >> 24).
So 64-bit math is needed. Would this be significantly slower than a float multiply?

user84634: OK, I took the trouble to code fixed-point routines and test them.  Here is the code ...

// fixed-point routines for 20 bit integer / 12-bit fraction
// format 0xfffff.fff, max value is +-524288.9998

#define FP_I 20     // integer bits,  e.g. 0xfffff.0
#define FP_F 12     // fraction bits, e.g. 0x0.fff
#define fpMul(_x,_y) ( (i32)( ((i64) _x * _y) >> FP_F) )
#define fpDiv(_x,_y) ( (i32)(  (((i64) _x) <<   32)      \
                             / (((i64) _y) >> FP_I) ) )
#define decToFp(_dec) ((i32) (_dec * 4096))   // e.g. decToFp(-1.2)    
#define fpToI32(_fp)  ((i32) (_fp  / 4096))

I did a 1000 loops and used the loop counter as the operation argument.  So each loop was one operation plus one floating-point increment.  I did this for fixed-point multiply, floating-point multiply, fixed-point divide, and floating-point divide.
The results were shocking.  The fixed-point multiply took almost exactly the same time as the floating-point. The fixed-point divide took 60% longer than the floating-point.  I went over my code and tried everything I could think of.  I built it in debug and release mode.  I tried 80MHz and 160MHz.  I found floating-point speeds on the web and my floating-point tests closely matched those.
I am at a loss as to how the 64-bit math could be slower than the floating-point.  Both are implemented in software.  I'm sure the floating-point is highly optimized with some tricks and coded in machine language.  But my fixed-point code is just shifts and 64-bit multiply/divides.  I don't think there are any tricks that could be used for it.
At least this makes the decision easy. I guess I'll use floating-point as sparingly as possible and cross my fingers that I can get my app to work fast enough.
I hope this is helpful to some passing-by googlers.  I couldn't find anything when I googled it.

user46852: Calculating the relative performance of 64 bit integer versus floating point multiplication is a little more difficult than it would appear.
It's easy to time a loop that does thousands of calculations, but unless you do something with the result the compiler will happily optimize them away. It's tempting to multiply constants, but the compiler will also happily optimize that away.
To keep the code as simple as possible and not introduce anything into the loop that would take more time, you need to disable or bypass the compiler's optimizations. An easy way to do that is to use the volatile modifier on the variables. volatile tells the compiler that it can't assume the variable hasn't changed, so the compiler will disable optimizations like caching the variable in a register or noticing that the same calculation is being done over and over again and just doing it once.
Using this technique, with a couple of tests to verify that the compiler would perform optimizations that interfere with timing, I get these results:
non-volatile uint32_t microseconds 1
constants uint32_t microseconds 5
uint16_t microseconds 162513
uint32_t microseconds 137513
uint64_t microseconds 1287533
int16_t microseconds 337513
int32_t microseconds 300023
int64_t microseconds 1287513
float  microseconds 912529

64 bit unsigned or signed multiplication takes about 1.4x the time as floating point multiplication.
The first test verified that non-volatile variables are optimized out. The second test verified that multiplying constants is also optimized out.
Turning off Wifi also helps keep test results consistent.
#include <Arduino.h>

#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif

#define TIMES_TO_LOOP 1000000

volatile uint16_t ux16, uy16, uresult16;
volatile uint32_t ux32, uy32, uresult32;
volatile uint64_t ux64, uy64, uresult64;

volatile int16_t x16, y16, result16;
volatile int32_t x32, y32, result32;
volatile int64_t x64, y64, result64;

volatile float xf, yf, resultf;

uint32_t x32n, y32n, result32n;

uint16_t seed16() {
  return random(0, 0xffff);
}

uint32_t seed32() {
  return random(0, 0xffffffff);
}

uint64_t seed64() {
  return seed32();
}

float seedfloat() {
  float x, y;

  x = seed32();
  y = seed32();

  return x / y;
}

void setup() {
  uint32_t i;
  uint64_t micros_start, micros_end;

  Serial.begin(115200);
  Serial.println("hello world");

  WiFi.mode( WIFI_OFF );

  delay(1000);

  x32n = seed32();
  y32n = seed32();

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    result32n = x32n * y32n;
  micros_end = micros();

  Serial.print("non-volatile uint32_t microseconds ");
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    result32n = 540000 * 15;
  micros_end = micros();

  Serial.print("constants uint32_t microseconds ");
  Serial.println(micros_end - micros_start);


  ux16 = seed16();
  uy16 = seed16();
  x16 = ux16;
  y16 = uy16;

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    uresult16 = ux16 * uy16;
  micros_end = micros();

  Serial.print("uint16_t microseconds ");
  Serial.println(micros_end - micros_start);
    
  ux32 = seed32();
  uy32 = seed32();
  x32 = ux32;
  y32 = uy32;

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    uresult32 = ux32 * uy32;
  micros_end = micros();

  Serial.print("uint32_t microseconds ");
  Serial.println(micros_end - micros_start);

  ux64 = seed64();
  uy64 = seed64();
  x64 = ux64;
  y64 = uy64;

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    uresult64 = ux64 * uy64;
  micros_end = micros();

  Serial.print("uint64_t microseconds ");
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    result16 = x16 * y16;
  micros_end = micros();

  Serial.print("int16_t microseconds ");
  Serial.println(micros_end - micros_start);
    
  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    result32 = x32 * y32;
  micros_end = micros();

  Serial.print("int32_t microseconds ");
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    result64 = x64 * y64;
  micros_end = micros();

  Serial.print("int64_t microseconds ");
  Serial.println(micros_end - micros_start);

  xf = seedfloat();
  yf = seedfloat();

  micros_start = micros();
  for(i = 0; i < TIMES_TO_LOOP; i++)
    resultf = xf * yf;
  micros_end = micros();

  Serial.print("float  microseconds ");
  Serial.println(micros_end - micros_start);
}

void loop() {
}


84634: <p>I need to do some math with vars from an attitude sensor (acclerometers and gyroscopes) on an esp8266. int32_t math with those vars does not have enough range and the float math operations are abyssmally slow.</p>
<p>I'm hoping to use a fixed-point format with 24 bits on the left of the decimal point and 8 bits on the right side.  So a number would be stored in a int32_t as 0xffffff.ff.  A typical operation would be a multiply like ((i64) x * y) &gt;&gt; 24).</p>
<p>So 64-bit math is needed. Would this be significantly slower than a float multiply?</p>
 84634: <p>OK, I took the trouble to code fixed-point routines and test them.  Here is the code ...</p>
<pre><code>
// fixed-point routines for 20 bit integer / 12-bit fraction
// format 0xfffff.fff, max value is +-524288.9998

#define FP_I 20     // integer bits,  e.g. 0xfffff.0
#define FP_F 12     // fraction bits, e.g. 0x0.fff
#define fpMul(_x,_y) ( (i32)( ((i64) _x * _y) >> FP_F) )
#define fpDiv(_x,_y) ( (i32)(  (((i64) _x) &lt;&lt;   32)      \
                             / (((i64) _y) &gt;&gt; FP_I) ) )
#define decToFp(_dec) ((i32) (_dec * 4096))   // e.g. decToFp(-1.2)    
#define fpToI32(_fp)  ((i32) (_fp  / 4096))
</code></pre>
<p>I did a 1000 loops and used the loop counter as the operation argument.  So each loop was one operation plus one floating-point increment.  I did this for fixed-point multiply, floating-point multiply, fixed-point divide, and floating-point divide.</p>
<p>The results were shocking.  The fixed-point multiply took almost exactly the same time as the floating-point. The fixed-point divide took 60% longer than the floating-point.  I went over my code and tried everything I could think of.  I built it in debug and release mode.  I tried 80MHz and 160MHz.  I found floating-point speeds on the web and my floating-point tests closely matched those.</p>
<p>I am at a loss as to how the 64-bit math could be slower than the floating-point.  Both are implemented in software.  I'm sure the floating-point is highly optimized with some tricks and coded in machine language.  But my fixed-point code is just shifts and 64-bit multiply/divides.  I don't think there are any tricks that could be used for it.</p>
<p>At least this makes the decision easy. I guess I'll use floating-point as sparingly as possible and cross my fingers that I can get my app to work fast enough.</p>
<p>I hope this is helpful to some passing-by googlers.  I couldn't find anything when I googled it.</p>
 46852: <p>Calculating the relative performance of 64 bit integer versus floating point multiplication is a little more difficult than it would appear.</p>
<p>It's easy to time a loop that does thousands of calculations, but unless you do something with the result the compiler will happily optimize them away. It's tempting to multiply constants, but the compiler will also happily optimize that away.</p>
<p>To keep the code as simple as possible and not introduce anything into the loop that would take more time, you need to disable or bypass the compiler's optimizations. An easy way to do that is to use the <code>volatile</code> modifier on the variables. <code>volatile</code> tells the compiler that it can't assume the variable hasn't changed, so the compiler will disable optimizations like caching the variable in a register or noticing that the same calculation is being done over and over again and just doing it once.</p>
<p>Using this technique, with a couple of tests to verify that the compiler would perform optimizations that interfere with timing, I get these results:</p>
<pre><code>non-volatile uint32_t microseconds 1
constants uint32_t microseconds 5
uint16_t microseconds 162513
uint32_t microseconds 137513
uint64_t microseconds 1287533
int16_t microseconds 337513
int32_t microseconds 300023
int64_t microseconds 1287513
float  microseconds 912529
</code></pre>
<p>64 bit unsigned or signed multiplication takes about 1.4x the time as floating point multiplication.</p>
<p>The first test verified that non-volatile variables are optimized out. The second test verified that multiplying constants is also optimized out.</p>
<p>Turning off Wifi also helps keep test results consistent.</p>
<pre><code>#include &lt;Arduino.h&gt;

#ifdef ESP32
#include &lt;WiFi.h&gt;
#else
#include &lt;ESP8266WiFi.h&gt;
#endif

#define TIMES_TO_LOOP 1000000

volatile uint16_t ux16, uy16, uresult16;
volatile uint32_t ux32, uy32, uresult32;
volatile uint64_t ux64, uy64, uresult64;

volatile int16_t x16, y16, result16;
volatile int32_t x32, y32, result32;
volatile int64_t x64, y64, result64;

volatile float xf, yf, resultf;

uint32_t x32n, y32n, result32n;

uint16_t seed16() {
  return random(0, 0xffff);
}

uint32_t seed32() {
  return random(0, 0xffffffff);
}

uint64_t seed64() {
  return seed32();
}

float seedfloat() {
  float x, y;

  x = seed32();
  y = seed32();

  return x / y;
}

void setup() {
  uint32_t i;
  uint64_t micros_start, micros_end;

  Serial.begin(115200);
  Serial.println(&quot;hello world&quot;);

  WiFi.mode( WIFI_OFF );

  delay(1000);

  x32n = seed32();
  y32n = seed32();

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    result32n = x32n * y32n;
  micros_end = micros();

  Serial.print(&quot;non-volatile uint32_t microseconds &quot;);
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    result32n = 540000 * 15;
  micros_end = micros();

  Serial.print(&quot;constants uint32_t microseconds &quot;);
  Serial.println(micros_end - micros_start);


  ux16 = seed16();
  uy16 = seed16();
  x16 = ux16;
  y16 = uy16;

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    uresult16 = ux16 * uy16;
  micros_end = micros();

  Serial.print(&quot;uint16_t microseconds &quot;);
  Serial.println(micros_end - micros_start);
    
  ux32 = seed32();
  uy32 = seed32();
  x32 = ux32;
  y32 = uy32;

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    uresult32 = ux32 * uy32;
  micros_end = micros();

  Serial.print(&quot;uint32_t microseconds &quot;);
  Serial.println(micros_end - micros_start);

  ux64 = seed64();
  uy64 = seed64();
  x64 = ux64;
  y64 = uy64;

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    uresult64 = ux64 * uy64;
  micros_end = micros();

  Serial.print(&quot;uint64_t microseconds &quot;);
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    result16 = x16 * y16;
  micros_end = micros();

  Serial.print(&quot;int16_t microseconds &quot;);
  Serial.println(micros_end - micros_start);
    
  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    result32 = x32 * y32;
  micros_end = micros();

  Serial.print(&quot;int32_t microseconds &quot;);
  Serial.println(micros_end - micros_start);

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    result64 = x64 * y64;
  micros_end = micros();

  Serial.print(&quot;int64_t microseconds &quot;);
  Serial.println(micros_end - micros_start);

  xf = seedfloat();
  yf = seedfloat();

  micros_start = micros();
  for(i = 0; i &lt; TIMES_TO_LOOP; i++)
    resultf = xf * yf;
  micros_end = micros();

  Serial.print(&quot;float  microseconds &quot;);
  Serial.println(micros_end - micros_start);
}

void loop() {
}
</code></pre>
