# Arduino vs. Significant Figures: Preliminary 64-bit Fixed Point Exercise

Although it’s not advertised, the Arduino / AVR compiler mostly does the right thing with `long long` = `uint64_t` variables: add & subtract work fine, but multiplication & division discard anything that doesn’t fit into 64 bits. Fitting a 32 bit integer and a 32 bit fraction into such a thing should eliminate (most) problems with significant figures.

The general idea is to set up a `struct` giving access to the two 32 bit halves for direct manipulation, then overlay / `union` them with a single 64 bit integer for arithmetic purposes:

```struct ll_s {
uint32_t low;
uint32_t high;
};

union ll_u {
uint64_t ll_64;
struct ll_s ll_32;
};
```

Of course, the integer part still falls one bit shy of holding 2³². At the cost of one bit’s worth of resolution, you can still compute 2³² / 125×10⁶ by pre-dividing each quantity by 2:

```2^63 = [80000000 00000000]
2^63 / 125/2 M = [00000022 5c17d04d]
```

The low-order digit should be 0xe, not 0xd, but I think that’s survivable.

Unfortunately, `printf` doesn’t handle 64 bit quantities, necessitating some awkward conversion routines. “Printing” to a string seems the least awful method, as I’ll eventually squirt the strings to a display, not send them to the serial port:

```void PrintFractionLL(char *pBuffer,uint64_t *pLL) {
uint64_t Fraction;

Fraction = (uint32_t)*pLL;                      // copy 32 fraction bits, high order = 0
Fraction *= ONEGIG;                             // times 10^9 for conversion
Fraction >>= 32;                                // align integer part in low long
sprintf(pBuffer,"%09lu",(uint32_t)Fraction);    // convert low long to decimal
}

void PrintIntegerLL(char *pBuffer,uint64_t *pLL) {
sprintf(pBuffer,"%lu",*((uint32_t *)pLL+1));
}

void PrintDecimalLL(char *pBuffer,uint64_t *pLL) {
PrintIntegerLL(pBuffer,pLL);
pBuffer += strlen(pBuffer);       // pointer to end of integer part
*pBuffer++ = '.';                 // drop in the decimal point, tick pointer
PrintFractionLL(pBuffer,pLL);
}
```

The result seems nearly indistinguishable from the Right Answer:

```Integer:  34
Fraction: 359738367
Decimal: 34.359738367
```

This whole mess has a bunch of rough edges, but it looks promising. The code coalesced while fiddling around, so the `union` notation didn’t get much love at first.

The Arduino source code as a GitHub Gist:

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 Long long integer exercise Ed Nisley - KE4ZNU - May 2017 Long long size = 8 bytes .. value = [12345678 9abcdef0] divided result = [01234567 89abcdef] 2^32 = [00000001 00000000] 125M = [00000000 07735940] 2^32 / 125M = [00000000 00000022] Scaled fixed point tests 2^63 = [80000000 00000000] 2^63 / 125/2 M = [00000022 5c17d04d] Integer: 34 Fraction: 359738367 Decimal: 34.359738367 Hz Per Ct: 0.029103830 60000: 60000.000000000 60 kHz as count: 2061584.302070550 0.1 Hz as count: 3.435973836 60000.1 Hz as count: 2061587.738044387 60000.1 Hz from count: 60000.078519806 60000.2 Hz as count: 2061591.174018223 60000.2 Hz from count: 60000.194935128 60000.2 Hz rnd count: 60000.224038958 Union size: 8 TestFreq: [0000ea60 395a9e00] Union ll: [0000ea60 395a9e00] From union: 60000.224038958 Buffer length: 15 Trunc dec: 60000.224
view raw LongLong.txt hosted with ❤ by GitHub
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 // Long long integer exercise for 60 kHz crystal tester //-- Helper routine for printf() int s_putc(char c, FILE *t) { Serial.write(c); } char Buffer[10+1+10]; // string buffer for long long conversions #define ONEGIG 1000000000LL uint64_t CtPerHz; // will be 2^32 / 125 MHz uint64_t HzPerCt; // will be 125 MHz / 2^32 uint64_t TenthHzCt; // 0.1 Hz in counts struct ll_s { uint32_t low; uint32_t high; }; union ll_u { uint64_t ll_64; struct ll_s ll_32; }; //----------- // Long long print-to-buffer helpers // Assumes little-Endian layout void PrintHexLL(char *pBuffer,uint64_t *pLL) { sprintf(pBuffer,"%08lx %08lx",*((uint32_t *)pLL+1),(uint32_t)*pLL); } // converts 9 decimal digits void PrintFractionLL(char *pBuffer,uint64_t *pLL) { uint64_t Fraction; Fraction = (uint32_t)*pLL; // copy 32 fraction bits, high order = 0 Fraction *= ONEGIG; // times 10^9 for conversion Fraction >>= 32; // align integer part in low long sprintf(pBuffer,"%09lu",(uint32_t)Fraction); // convert low long to decimal } void PrintIntegerLL(char *pBuffer,uint64_t *pLL) { sprintf(pBuffer,"%lu",*((uint32_t *)pLL+1)); } void PrintDecimalLL(char *pBuffer,uint64_t *pLL) { PrintIntegerLL(pBuffer,pLL); pBuffer += strlen(pBuffer); // pointer to end of integer part *pBuffer++ = '.'; // drop in the decimal point, tick pointer PrintFractionLL(pBuffer,pLL); } //----------- void setup () { Serial.begin (115200); fdevopen(&s_putc,0); // set up serial output for printf() Serial.println ("Long long integer exercise"); Serial.println ("Ed Nisley - KE4ZNU - May 2017"); unsigned long long LongLong; uint64_t LongLong2; LongLong = 0x123456789abcdef0LL; printf("Long long size = %d bytes\n",sizeof(LongLong)); printf(" .. value = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)(LongLong & 0x00000000ffffffffLL)); LongLong /= 16; printf(" divided result = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong); LongLong = 1LL << 32; printf(" 2^32 = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong); LongLong2 = 125000000LL; printf(" 125M = [%08lx %08lx]\n",(long)(LongLong2 >> 32),(long)LongLong2); LongLong /= LongLong2; printf("2^32 / 125M = [%08lx %08lx]\n",(long)(LongLong >> 32),(long)LongLong); Serial.println("Scaled fixed point tests"); uint64_t TestFreq,TestCount; CtPerHz = 1LL << 63; // start with 2^31 to avoid overflow PrintHexLL(Buffer,&CtPerHz); printf("2^63 = [%s]\n",Buffer); CtPerHz /= 125000000LL / 2; // divided by 2 to to match 2^31 PrintHexLL(Buffer,&CtPerHz); printf("2^63 / 125/2 M = [%s]\n",Buffer); PrintIntegerLL(Buffer,&CtPerHz); printf("Integer: %s\n",Buffer); PrintFractionLL(Buffer,&CtPerHz); printf("Fraction: %s\n",Buffer); PrintDecimalLL(Buffer,&CtPerHz); printf("Decimal: %s\n",Buffer); HzPerCt = 125000000LL; // 125 MHz / 2^32, directly to fraction part PrintDecimalLL(Buffer,&HzPerCt); printf("Hz Per Ct: %s\n",Buffer); TestFreq = 60000LL << 32; PrintDecimalLL(Buffer,&TestFreq); printf("60000: %s\n",Buffer); TestCount = 60000LL * CtPerHz; PrintDecimalLL(Buffer,&TestCount); printf("60 kHz as count: %s\n",Buffer); TenthHzCt = CtPerHz / 10; // 0.1 Hz as counts PrintDecimalLL(Buffer,&TenthHzCt); printf("0.1 Hz as count: %s\n",Buffer); TestCount += TenthHzCt; PrintDecimalLL(Buffer,&TestCount); printf("60000.1 Hz as count: %s\n",Buffer); TestFreq = (TestCount >> 32) * HzPerCt; PrintDecimalLL(Buffer,&TestFreq); printf("60000.1 Hz from count: %s\n",Buffer); TestCount = (60000LL * CtPerHz) + (2 * TenthHzCt); PrintDecimalLL(Buffer,&TestCount); printf("60000.2 Hz as count: %s\n",Buffer); TestFreq = (TestCount >> 32) * HzPerCt; PrintDecimalLL(Buffer,&TestFreq); printf("60000.2 Hz from count: %s\n",Buffer); TestFreq = ((TestCount + (TenthHzCt / 2)) >> 32) * HzPerCt; PrintDecimalLL(Buffer,&TestFreq); printf("60000.2 Hz rnd count: %s\n",Buffer); union ll_u LongLongUnion, TempLLU; printf("Union size: %d\n",sizeof(LongLongUnion)); LongLongUnion.ll_64 = TestFreq; PrintHexLL(Buffer,&TestFreq); printf("TestFreq: [%s]\n",Buffer); PrintHexLL(Buffer,&LongLongUnion.ll_64); printf("Union ll: [%s]\n",Buffer); TempLLU.ll_64 = LongLongUnion.ll_32.low; TempLLU.ll_64 *= ONEGIG; // times 10^9 for conversion TempLLU.ll_64 >>= 32; // align integer part in low long sprintf(Buffer,"%lu.%09lu",LongLongUnion.ll_32.high,TempLLU.ll_32.low); printf("From union: %s\n",Buffer); sprintf(Buffer,"%lu.%09lu",LongLongUnion.ll_32.high,TempLLU.ll_32.low); printf("Buffer length: %d\n",strlen(Buffer)); Buffer[strlen(Buffer) - 9 + 3] = 0; printf(" Trunc dec: %s\n",Buffer); } //----------- void loop () { }

## 3 thoughts on “Arduino vs. Significant Figures: Preliminary 64-bit Fixed Point Exercise”

1. William Rutiser says:

What tool chain do use for Arduino work?
You have inspired me to at least find and dump out the contents of a box of Arduino stuff from Dec 2010.

1. Ed says:

Not much of a chain: the plain old Arduino IDE set to “use external editor” on the landscape monitor and KDE fullscreen on the portrait monitor. Save the file, click upload, and away it goes.

The bone-stock IDE lets me get away with columns / article minus all the verbiage explaining how to squirt programs into microcontrollers.