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:
| 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 |
| // 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 () { | |
| } | |


















