Use of extern in in the implementation of double floats from an external library

Hi All,

I am missing something when it comes to using the extern keyword. I have an interest in the usage of double precision floats even though they are a stretch on an 8 bit AVR machine. By default Arduino processes doubles as floats. I have researched this topic and there are good arguments for using longs or even quads and managing the processes as fixed point. Perhaps use of BigNums? Sometimes this works and is a good solution but other times it is is less satisfactory. I was on the lookout for an implementation of double floats. Double floats hit the mark when it comes to magnitude and precision. This avenue of investigation might turn out to be useless but it is bound to teach me something.

The result of the searching was this reference:
http://forum.arduino.cc/index.php?topic=106614.0;wap2
which links to:
http://www.mikrocontroller.net/topic/85256
which, for me at least is unfortunate , as it is in German. However Google translate makes a fair fist of it and there are worse languages to guess at than German.

This leads to a download: called IEEE754_double.zip.

In this zip file there are are a number of files, however the two of interest are avr_f64.c and avr_f64.h. This looked like I might be able to call it as a library so I created a folder called avr_f64 in the library folder of the Arduino structure and placed the above files in this folder. I then created a small test program:

#include <avr_f64.h>
float64_t a,b,c;
void setup()
{
}

void loop()
{
}

And this complied just fine. Clearly the new type float64_t was recognized. So now to do something with it. I added just one line so the code looks this:
#include <avr_f64.h>

float64_t a,b,c;
void setup()
{
c = f_add(a,b); // where f_add add adds two doubles and returns the result
}

void loop()
{
}

I realize that this won't do anything sensible as a and b are undefined. However I wanted see if it would at least compile. Which it didn't. The IDE reported:

d1.cpp.o: In function setup': C:\DOCUME~1\Fred\LOCALS~1\Temp\build616389023816200979.tmp/d1.cpp:10: undefined reference to f_add(unsigned long long, unsigned long long)'

Some research (see Found a 64bit double implementation library, but I cannot compile it - Programming Questions - Arduino Forum) on this indicted that it was because the function f_add is a C function and not C++. As a result the "mangling" done to allow overloading in C++ was not done and the linker cannot find the un-mangled function name. The fix is to use an extern keyword to tell the the linker that the function is C not C++ and therefore not to expect mangling. If I read this correctly I need to amend the .h file (see lines 134+ in the .h file).

The fragment of the .h file in question starts off like this:

float64_t f_add(float64_t a, float64_t b); // Returns a+b . Special case: -INF + INF = NaN
float64_t f_sub(float64_t a, float64_t b); // Returns a-b . Special case: INF - INF = NaN
float64_t f_mult(float64_t fa, float64_t fb); // Returns a*b . Special case: +/-INF * 0 = NaN
float64_t f_div(float64_t x, float64_t y); // Returns a/b . Special cases: x/0=NaN , INF/INF = NaN , x/INF = 0 if x!=+/-INF

This shows the definitions of the add, subtract, multiply and divide cases. My research shows I have options in defining how the extern might look:

for example:
extern "C"
{
float64_t f_add(float64_t a, float64_t b); // Returns a+b . Special case: -INF + INF = NaN
float64_t f_sub(float64_t a, float64_t b); // Returns a-b . Special case: INF - INF = NaN
float64_t f_mult(float64_t fa, float64_t fb); // Returns a*b . Special case: +/-INF * 0 = NaN
float64_t f_div(float64_t x, float64_t y); // Returns a/b . Special cases: x/0=NaN , INF/INF = NaN , x/INF = 0 if x!=+/-INF
}

should define all these as externals in C.

However it can also be made to work for f_add only:
extern "C" float64_t f_add(float64_t a, float64_t b); // Returns a+b . Special case: -INF + INF = NaN

So the equivalent fragment looks like this:
extern "C" float64_t f_add(float64_t a, float64_t b); // Returns a+b . Special case: -INF + INF = NaN
float64_t f_sub(float64_t a, float64_t b); // Returns a-b . Special case: INF - INF = NaN
float64_t f_mult(float64_t fa, float64_t fb); // Returns a*b . Special case: +/-INF * 0 = NaN
float64_t f_div(float64_t x, float64_t y); // Returns a/b . Special cases: x/0=NaN , INF/INF = NaN , x/INF = 0 if x!=+/-INF

So when I try the latter case I get a new error:
In file included from C:\arduino-1.0\libraries\avr_f64\avr_f64.c:62:
C:\arduino-1.0\libraries\avr_f64/avr_f64.h:134: error: expected identifier or '(' before string constant

So it seems to want an identifier or a ( before the "C" which is what I am am assuming is the string constant. I don't believe this and think I have missed something in using extern.

If I try the first construct:
extern "C"
{
float64_t f_add(float64_t a, float64_t b); // Returns a+b . Special case: -INF + INF = NaN
float64_t f_sub(float64_t a, float64_t b); // Returns a-b . Special case: INF - INF = NaN
float64_t f_mult(float64_t fa, float64_t fb); // Returns a*b . Special case: +/-INF * 0 = NaN
float64_t f_div(float64_t x, float64_t y); // Returns a/b . Special cases: x/0=NaN , INF/INF = NaN , x/INF = 0 if x!=+/-INF
}

the error reported is the same.

What am I missing??

Thanks in advance, Regards, Fred.

Can you post the .h and .c files? There doesn't appear to be anything wrong with what you are doing, but it's hard to be sure, because we don't know the line numbers in the file, from the snippets you posted.

The short answer is you need to do this...

#ifdef __cplusplus
extern "C" {
#endif
float64_t f_add(float64_t a, float64_t b);      // Returns a+b . Special case: -INF + INF = NaN
float64_t f_sub(float64_t a, float64_t b);      // Returns a-b . Special case:  INF - INF = NaN
float64_t f_mult(float64_t fa, float64_t fb);   // Returns a*b . Special case: +/-INF * 0 = NaN
float64_t f_div(float64_t x, float64_t y);      // Returns a/b . Special cases: x/0=NaN , INF/INF = NaN , x/INF = 0 if x!=+/-INF
#ifdef __cplusplus
}
#endif

The long answer...

The ifdef statement is needed because the header file is seen by both the C++ compiler, which does name mangling (to support overloaded methods) and the C compiler, which does not. I'd forgotten that step; it's in the header file I looked at to confirm that the syntax was right, but I glossed over why it was there.

I would say move up to the Arm processors that support 64-bit doubles directly. Some of them (like Teensy 3.0/3.1, Arduino Due, and Digistump DigiX) are programmed with variants of the Arduino IDE, so you can move most code over easily. Note, the Arm boards run at 3.3v instead of 5v, so you may need to deal with this with things that need 5v.

However note, all of the above chips emulate floating point. If you are doing a lot of FP calculations, I would recommend going up to the processors that run embedded Linux like Rasberry Pi, Beagle Bone Black, or pcDunio. These processors run at a higher clock rate, have more memory, have native floating point instructions. If you need to do real time processing, considering adding an Arduino as a slave processor to do the real time stuff.

Recently there was a kickstarter project for selling a Sparkcore that had a Spark V9 chip in it. The project has ended, but I don't know if it is now available via retail sales.

Another kickstarter project that I just noticed is going on and is currently active, has an Arm processor with a floating point unit. It has passed the point where the project will be funded, and it claims delivery in August (note KS delivery estimates are just that, and most of the KS campaigns I have funded have been late): https://www.kickstarter.com/projects/1756272518/attoduino-turbocharged-wireless-arduino-compatible?ref=discovery

As a rule double precision math is twice slower compared to single precision on any architecture (done in sw). You may use it on the 8bitters happily too..

According to the various entries in the forum post describing the package 64 Bit float Emulator in C, IEEE754 compatibel - Mikrocontroller.net you can expect about 200 64-bit float divisions per second and about 1000 64-bit float multiplications on a 16 MHz ATmega.

But when you get to double precision done in hardware, it often is the same speed or faster to do double precision than single precision, for most calculations other than division and square roots.

Hi All,

Thanks for the assistance. I now have AVR_F64 running.

For the record Ifound the reference in a post in avrfreaks - towards the bottom by Bingo600 May 18, 2010 which gives the reference to the original at mikrocontroller.net at:
http://www.mikrocontroller.net/topic/85256

This is a German site and it helps if you have some skills in Deutsch. Google translate does not do a bad job though. Work was done by another on this system to provide some optimization of speed vs size and there is an updated .c file at:
http://www.mikrocontroller.net/attachment/51020/avr_f64.c

If you want to use this library from the Arduino IDE it needs some editing of the .h file. For those who might be interested here is the .h file with the extern statement in a place where it works. (There may be a better place - I am no expert). Note also I have enabled all of the functions. Each function can be turned on or off with an #DEFINE but if you want to experiment this is a nuisance. To turn these off just comment out the #DEFINE associated with the unwanted function. I have attached the .h file.

I read through the .c code as well and there are many comments in German. I have translated these to my ability and I hope they are of assistance to someone. I used Google translate but also applied a sanity test. Google translate struggles where there are computer variables interspersed with the description. If there are native speakers of German with the requisite English and C skills I am sure that they could do a better job than me. Attached is the c file with the German and translated comments in parallel.

Finally I built a table of the functions and what they do as a reference. It is attached as a excel spreadsheet.

Returning to the comments:

Thanks to David Oconnor and PaulS. I have read both the short and long explanations and am somewhat wiser. This space has many traps for neophytes.

Should most of my application require a decent double floating capability and speed I can only agree with MichaelMeissner that a better solution would be one based on a processor with an inherent hardware capability and there are many of these including the raspberry PI. (I have a couple of these but not doing process control stuff). However in the case in point the calculations are very few and far between and could be done sequentially through loop() a chunk at a time so that the processor is not slugged with a massive demand. This will be quite satisfactory in this application as the result is not needed instantly. If the problem were more intensive a better processor would be indicated. As to the 3.3V vs 5 V issue I have that already only going from 5 to 3.3 in this application. I expect to see more of this. Thanks also to jremington whose German looks to be better than mine.

Thanks again for all the help.
Regards,
Fred.

avr_f64.h (14.6 KB)

avr_f64.c (63.5 KB)

functions.xlsx (15.6 KB)