-
Arduino vs. Significant Figures: BigNumber Library
The BigNumber library wraps the
bcarbitrary precision calculator into a set of Arduino routines that seem like a reasonable basis for DDS calculations requiring more than the half-dozen digits of a floating point number or the limited range of scaled fixed point numbers tucked into anlong int.Treating programming as an experimental science produces some Arduino source code and its output as a GitHub Gist:
This file contains hidden or 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// BigNumber exercise #include "BigNumber.h" //– Helper routine for printf() int s_putc(char c, FILE *t) { Serial.write(c); } void setup () { Serial.begin (115200); fdevopen(&s_putc,0); // set up serial output for printf() Serial.println ("BigNumber exercise"); Serial.println ("Ed Nisley – KE4ZNU – April 2017"); #define WHOLES 10 #define FRACTS 10 printf("Fraction digits: %d\n",FRACTS); BigNumber::begin (FRACTS); char *pBigNumber; #define BUFFLEN (WHOLES + FRACTS) char NumString[BUFFLEN]; BigNumber Tenth = "0.1"; // useful constants BigNumber Half = "0.5"; BigNumber One = 1; BigNumber Two = 2; BigNumber ThirtyTwoBits = Two.pow(32); Serial.println(ThirtyTwoBits); BigNumber Oscillator = "125000000"; Serial.println(Oscillator); BigNumber HertzPerCount; HertzPerCount = Oscillator / ThirtyTwoBits; Serial.println(HertzPerCount); BigNumber CountPerHertz; CountPerHertz = ThirtyTwoBits / Oscillator; Serial.println(CountPerHertz); BigNumber TestFreq = "60000"; Serial.println(TestFreq); BigNumber DeltaPhi; DeltaPhi = TestFreq * CountPerHertz; Serial.println(DeltaPhi); long DeltaPhiL; DeltaPhiL = DeltaPhi; printf("Long: %ld\n",DeltaPhiL); Serial.println("0.1 Hz increment …"); Serial.println(TestFreq + Tenth); DeltaPhi = (TestFreq + Tenth) * CountPerHertz; Serial.println(DeltaPhi); TestFreq = DeltaPhi * HertzPerCount; Serial.println(TestFreq); Serial.println("Rounding DeltaPhi up …"); DeltaPhi += Half; Serial.println(DeltaPhi); TestFreq = DeltaPhi * HertzPerCount; Serial.println(TestFreq); pBigNumber = DeltaPhi.toString(); printf("String: %04x → %s\n",pBigNumber,pBigNumber); free(pBigNumber); DeltaPhiL = DeltaPhi; printf("Unsigned: %ld\n",DeltaPhiL); pBigNumber = "59999.9"; TestFreq = pBigNumber; Serial.println(TestFreq); DeltaPhi = TestFreq * CountPerHertz; Serial.println(DeltaPhi); Serial.println("Rounding DeltaPhi up …"); DeltaPhi = TestFreq * CountPerHertz + Half; Serial.println(DeltaPhi); DeltaPhiL = DeltaPhi; int rc = snprintf(NumString,BUFFLEN,"%ld",DeltaPhiL); if (rc > 0 && rc < BUFFLEN) { printf("String length: %d\n",rc); } else { printf("Whoops: %d for %ld\n",rc,DeltaPhiL); strncpy(NumString,"123456789",sizeof(NumString)); NumString[BUFFLEN-1] = 0; printf(" forced: %s\n",NumString); } printf("Back from string [%s]\n",NumString); DeltaPhi = NumString; Serial.println(DeltaPhi); TestFreq = DeltaPhi * HertzPerCount; Serial.println(TestFreq); } void loop () { } This file contains hidden or 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 charactersBigNumber exercise Ed Nisley – KE4ZNU – April 2017 Fraction digits: 10 4294967296 125000000 0.0291038304 34.3597383680 60000 2061584.3020800000 Long: 2061584 0.1 Hz increment … 60000.1000000000 2061587.7380538368 60000.0998830384 Rounding DeltaPhi up … 2061588.2380538368 60000.1144349536 String: 045e → 2061588.2380538368 Unsigned: 2061588 59999.9 2061580.8661061632 Rounding DeltaPhi up … 2061581.3661061632 String length: 7 Back from string [2061581] 2061581 59999.9037798624 All that happened incrementally, as you might expect, with the intent of seeing how it works, rather than actually doing anything.
Some musings, in no particular order:
The library soaks up quite a hunk of program space:
Sketch uses 13304 bytes (43%) of program storage space. Maximum is 30720 bytes.
I think you could cut that back a little by eliminating unused
bcroutines, like square root / exponential / modulus.That test code also blots up quite a bit of RAM:
Global variables use 508 bytes (24%) of dynamic memory, leaving 1540 bytes for local variables. Maximum is 2048 bytes.
All the
BigNumbervariables live inside thesetup()function (or whatever it’s called in Arduino-speak), so they count as local variables. They’re four bytes each, excluding the dynamically allocated storage for the actual numbers at roughly a byte per digit. With 10 decimal places for all numbers, plus (maybe) an average of half a dozen integer digits, those tenBigNumberssoak up200 = 10 × (4 + 16)bytes of precious RAM.You can load a
BigNumberfrom anint(not along) or a string, then export the results to alongor a string. Given that controlling a DDS frequency with a knob involves mostly adding and subtracting a specific step size, strings would probably work fine, usingsnprintf()to jam the string equivalent of alonginto aBigNumberas needed.You must have about ten decimal places to hold enough significant figures in the
HertzPerCountandCountPerHertzvalues. The library scale factor evidently forces all the numbers to have at least that many digits, with the decimal point stuck in front of them during string output conversions.The biggest integers happen in the
OscillatorandThirtyTwoBitsvalues, with 9 and 10 digits, respectively.It looks useful, although I’m uncomfortable with the program space required. I have no way to estimate the program space for a simpleminded DDS controller, other than knowing it’ll be more than I estimate.
While poking around, however, I discovered the Arduino compiler does provide (limited) support for
long long intvariables. Given a 64 bit unit for simple arithmetic operations, a simpler implementation of fixed point numbers may be do-able: 32 bits for the integer and fraction should suffice! More on that shortly.