If I could lob this into the discussion... 
It is convenient that the sample is scaled by 219, as this makes converting it to a decimal whole and fractional part very easy. The whole part is simply
sample >> 19
...and the fractional part is
sample & 0x0007FFFF
Here is a sketch that uses integers only, and provides 8 or 9 significant digits (6 decimals). It prints the whole and fractional parts with a '.' between them, so it "looks" like a floating-point number. It can also be built without using float
, to compare the sketch sizes:
// Comment this out to build without the floating-point library
#define DO_FLOAT_CALC
// define variables
uint32_t CapOffset = 90;
const uint8_t ITERATIONS = 128;
//------------
void intCalc( uint32_t sample, uint32_t &whole, uint32_t &frac )
{
// sample is 0 (74pF) to 13631488 (100pF)
whole = sample >> 19; // same as divide by 524288
whole = whole + CapOffset - 16;
uint8_t frac_hi = ((uint16_t)(sample >> 16)) & 0x0007; // upper 3 bits
uint16_t frac_lo = sample & 0xFFFF;
const uint32_t E6 = 1000000UL;
const uint8_t E6_hi = E6 >> 16;
const uint16_t E6_lo = E6 & 0xFFFF;
uint32_t frac_Q3 = (((uint32_t)(frac_hi * E6_hi)) << 16) + // F
((((uint32_t)frac_hi * E6_lo)) ) + // O
((((uint32_t)frac_lo * E6_hi)) ) + // I
((((uint32_t)frac_lo * E6_lo)) >> 16); // L
frac = frac_Q3 >> 3;
}
//------------
#ifdef DO_FLOAT_CALC
float result;
void floatCalc( uint32_t sample )
{
result = (sample / 524288.0) + CapOffset - 16;
}
#endif
//------------
void printCap( uint32_t whole, uint32_t frac )
{
Serial.print( whole );
Serial.print( '.' );
if (frac < 10L)
Serial.print( F("00000") );
else if (frac < 100L)
Serial.print( F("0000") );
else if (frac < 1000L)
Serial.print( F("000") );
else if (frac < 10000L)
Serial.print( F("00") );
else if (frac < 100000L)
Serial.print( F("0") );
Serial.print( frac );
}
//------------
void timeCalc( uint32_t sample )
{
uint32_t whole, frac;
uint32_t sampleTime;
// long integers only
sampleTime = micros();
for (uint8_t i=ITERATIONS; i > 0; i--) {
intCalc( sample+i, whole, frac );
}
uint32_t intCalcTime = (micros() - sampleTime) / ITERATIONS;
printCap( whole, frac );
Serial.println();
#ifdef DO_FLOAT_CALC
// original code
sampleTime = micros();
for (uint8_t i=ITERATIONS; i > 0; i--) {
floatCalc( sample+1 );
}
uint32_t floatCalcTime = (micros() - sampleTime) / ITERATIONS;
Serial.println( result, 5 );
#endif
Serial.print( intCalcTime );
#ifdef DO_FLOAT_CALC
Serial.print( F("us vs. ") );
Serial.print( floatCalcTime );
#endif
Serial.println( F("us") );
Serial.flush(); // wait for all those character to be sent and displayed
}
//------------
void setup() {
Serial.begin(9600);
timeCalc( 0 );
timeCalc( 524288 );
timeCalc( 9913409 );
timeCalc( 13631488 );
}
void loop() {}
Because the 32-bit multiplication of the fractional part by 106 can overflow a long integer, it is performed with safe 16-bit multiplications. Each 32-bit number is broken into two parts:
(a + b)(c + d)
Where
a = upper 16 bits of sample fraction (always <= 0x0007)
b = lower 16 bits of sample fraction
c = upper 16 bits of 1000000 factor (always 0x000F)
d = lower 16 bits of 1000000 factor (always 0x4240)
Using our favourite FOIL technique, we can safely get the 32-bit sum of the 4 products. With these conditions, a*c
can be an 8-bit multiply (7 * 15 = 105, which fits), but the others must be 32-bit multiplies.
Because a
and c
are scaled by 216, the addition has to shift the products before they can be added:
(a*c * 2^32) + (a*d * 2^16) + (b*c * 2^16) + (b*d)
However, that would overflow on the top, so lets truncate the bottom 16 bits instead:
(a*c * 2^16) + (a*d) + (b*c) + (b*d / 2^16)
That also scales down by 16 bits of the 19 that are needed for the final result. Shifting the sum by another 3 bits takes care of the rest.
Although you don't need the speed, this is about 10% faster. 
Cheers,
/dev