How to print 64bit float in teensyduino for teensy 4.1

manes6969

Member
Hi,

I'm trying to print a float64 number with more than 7 decimals , but without any success. Whatever I try , I'm not getting more than 7 decimals.
This is my code :

//--------------------------------
//64 bit float test for Teensy 4.1
//--------------------------------

#include <arm_math.h>

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
// put your main code here, to run repeatedly:
double number1 = 1.00000001L;
double number2 = 1.000000005L;
float number3 = 22.0L;
float number4 = 7.0L;
Serial.printf("%d\n", (number1 f+ number2));
Serial.printf("%lf\n", number3/number4);

}

I'm reading on many internet searches that arduino IDE can only handle 32 bit floats . Is this true ??

Any help welcome !
Regards,
Ronny
 
This works for 64 bit values as used here for integers: >> "%llu"

Code:
	Serial.printf("Bytes Used: %llu, Bytes Total:%llu\n", myfs.usedSize(), myfs.totalSize());
 
Hi defragster

This gives a strange result , changed code to :

//--------------------------------
//64 bit float test for Teensy 4.1
//--------------------------------

#include <arm_math.h>

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
// put your main code here, to run repeatedly:
double number1 = 1.00000001L;
double number2 = 1.000000005L;
float number3 = 22.0L;
float number4 = 7.0L;

Serial.printf("%llu\n", number3/number4);

result is : 4614259503775350784
 
The format specifier for double is %lf
For float it is %f
So, your printf is in double way wrong ;) number 3 and number 4 are floats, so the result is float (-> use %f) ... %llu is for unsigned long long

%1.9lf prints your 1.000000005 correctly.

For printf format specifiers: Google is your friend
 
Hi Frank ,

almost there , but not correct results. it looks like calculation is done in 32 bit (?)

//--------------------------------
//64 bit float test for Teensy 4.1
//--------------------------------

#include <math.h>

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
// put your main code here, to run repeatedly:
float number1 = 1.00000001L;
float number2 = 1.000000005L;
float number3 = 22.0;
float number4 = 7.0;

Serial.printf("%1.9lf\n", number1+number2); // prints 2.000000000 -> wrong !
Serial.printf("%1.9lf\n", number3/number4); // prints 3.142857075 -> wrong !

Any ideas remaining ... ?
Ronny
 
You've declared them all as float. The addition and division are done as float with the result having float precision which is only about 7 digits.
Declare them as double or cast them.

Pete
 
This "%1.9lf" might cause confusion too. If you want double it should be something like "%24.20lf".

Pete
 
Hi Pete ,

Thanks , this finally works !!!
Most forums state that float and double is the same , but apparently it isn't.
This double and float and long are somewhat confusing me.
Thanks again and also thanks to the other members for their help.

Wishes ,
Ronny
 
float and double are the same on most Arduinos and Teensys before 4.0 because "double" arithmetic is very slow on processors which don't have the double precision hardware. It also saved precious ram space on early 8-bit Arduinos (Uno, Nano) and Teensys (up to T++2).
But Teensy4.x have double precision arithmetic in hardware and lots of ram.

Pete
 
Hi Pete ,

Thanks , this finally works !!!
Most forums state that float and double is the same , but apparently it isn't.
This double and float and long are somewhat confusing me.
Thanks again and also thanks to the other members for their help.

Wishes ,
Ronny

Could you share how your sketch finally turned out?
 
I tried this just now.

Code:
void setup() {
  Serial.begin(9600);
}

void loop() {
  double number1 = 1.00000001L;
  double number2 = 1.000000005L;
  double number3 = 22.0;
  double number4 = 7.0;

  Serial.printf("%12.9lf\n", number1 + number2);
  Serial.printf("%12.9lf\n", number3 / number4);
  Serial.println();
  delay(1000);
}

Running on a Teensy 4.1, it prints this in the serial monitor

Code:
 2.000000015
 3.142857143

 2.000000015
 3.142857143

 2.000000015
 3.142857143

 2.000000015
 3.142857143

 2.000000015
 3.142857143

 2.000000015
 3.142857143
 
It's fine when you define the double variable, as in:
Code:
double Lat = 1.00000001L
But when that value comes from another source, such as some data thrown by a sensor or by some previous mathematical operation, how is it possible to preserve the value of X as double?.

Code:
#include "ILI9341_t3.h"
#include <SPI.h>

#define TFT_RST  8
#define TFT_DC   9
#define TFT_CS  10
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST);

#define TFT_RST2 21 
#define TFT_DC2  20
#define TFT_CS2  15
ILI9341_t3 tft2 = ILI9341_t3(TFT_CS2, TFT_DC2, TFT_RST2);


char floatTXT[40];

void setup ()
{
  Serial.begin (115200);
  Serial.println ();

  tft.begin();
  tft.setRotation(3); 
  tft.fillScreen(ILI9341_BLUE);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.setCursor(320-80,10);
  tft.print("First TFT");
  
  tft2.begin();
  tft2.setRotation(3);
  tft2.fillScreen(ILI9341_RED);
  tft2.setTextColor(ILI9341_WHITE);
  tft2.setTextSize(1);
  tft2.setCursor(320-80,10);
  tft2.print("Second TFT"); 
  
  double Lat= 1.00000001L, Long= 1.000000005L;
          
  tft.setTextSize(2);
  Serial.println();
  Serial.print ("(");  Serial.print (Lat,9);  Serial.print (", ");   Serial.print (Long,9);  Serial.print (")");  
  tft.setCursor(0,50);  tft.print("(");  tft.print(Lat,9);  tft.print(", ");  tft.print(Long,9);  tft.print(")");

  double XLat, YLong;  //sensor 
  XLat= 1.00000001;  YLong= 1.000000005;
  Lat=XLat; Long=YLong;
    
  Serial.println();
  sprintf(floatTXT,"(%.9f, %.9f)", Lat, Long);
  Serial.println (floatTXT);
  tft.setCursor(0,75);   tft.print(floatTXT);
}
void loop(){}

ydmn3qplc8iqwegzg.jpg


Code:
double Lat= 1.00000001L, Long= 1.000000005L;
Works fine!

Code:
double XLat, YLong;  //sensor 
 XLat= 1.00000001;  YLong= 1.000000005;
 Lat=XLat; Long=YLong;
Yes please, Don´t work!

Is it possible to add by code the ending "L" to a double value obtained from a sensor or from some mathematical operation within the sketch itself?

PS: Excuse me for asking so much, but there are things that not even Google San understands when you ask!
 
Maybe you're using Teensy LC or 3.x?

On those boards, constants default to 32 bit float, even if you assign them to 64 bit double.

This is done to prevent casually typed float constants (without a trailing "f") from being treated at 64 bits and automatically converting all the surrounding math up to 64 bits. The FPU on Teensy 3.5 & 3.6 only handles 32 bit float, and on LC & 3.2 with no FPU, software is much faster at 32 bit float.

On Teensy LC & 3.x, you need to add the "L" for a constant to be treated as 64 bits. Yes, it's against the C standard and it's inconvenient, but overall the trade-off is most people use only 32 bits and this prevents very common usage from spoiling 32 bit float performance.
 
In teensy 3.6 it doesn't work, but in teensy 4 it does:

k9j6nw2fsr4kdg7zg.jpg


Code:
  double XLat, YLong;  //sensor 
  XLat= 1.00000001;  YLong= 1.000000005;
  Lat=XLat; Long=YLong;
    
  Serial.println();
  Serial.printf ("(");  Serial.printf ("%.9lf",Lat);  Serial.printf (", ");   Serial.printf ("%.9lf",Long);  Serial.printf (")");  
  sprintf(floatTXT,"(%.9lf, %.9lf)", Lat, Long);
  tft.setCursor(0,75);   tft.print(floatTXT);

Sure enough, you're right, I was working the code on a teensy 3.6. When implemented in the teensy 4 everything goes as you have indicated. Thank you.
 
Try changing this:

Code:
  XLat= 1.00000001;  YLong= 1.000000005;

to this:

Code:
  XLat= 1.00000001LL;  YLong= 1.000000005LL;

It will work on Teensy 3.6 when you get the syntax right.
 
To take advantage of the topic started by manes6969, I took what I learned.

I moved towards improving the code for the GPS with what was exposed here. Previously I used only float, because the use of double was not clear to me.

Teensy 4 + NHD FT813 3.5"
7z9lgqa3ijn5gjxzg.jpg


The first line uses double correction, the second line uses float.

Code:
  double Lat, Long;
  Lat= 19.485155;  Long= -99.831861;  //GPS
    
  //Serial.println();
  //Serial.printf ("(");  Serial.printf ("%.9lf",Lat);  Serial.printf (", ");   Serial.printf ("%.9lf",Long);  Serial.printf (")");  
  sprintf(floatGPS,"(%.9lf,%.9lf)", Lat, Long);
  GD.cmd_text(GD.w / 2, GD.h / 2, 28, OPT_CENTER, floatGPS);

  float FLat = Lat, FLong = Long;
  sprintf(floatGPS,"(%.9lf,%.9lf)", FLat, FLong);
  GD.cmd_text(GD.w / 2, (GD.h / 2)+25, 28, OPT_CENTER, floatGPS);

Float always introduces errors in digits beyond the sixth, which makes it difficult to implement complex calculations, such as determining the distance between two pairs of coordinates.

THX again
 
And note, I believe the code will not work on Teensy 4.0/4.1 if you optimize for space, since the sprintf when optimizing for space omits the floating point formatting stuff. Also of course, it might not work on other processors using Arduino framework.

To that end, I wrote this fragment of code for my power meter that uses an INA219 sensor to read the amount of power used:
Code:
static const int	BUFFER_SIZE		= 40;
static const unsigned	MAX_COUNTER		= (500 / DEBOUNCE);
static unsigned int	display_counter		= MAX_COUNTER;

// prevent compiler from optimizing sprintf test
volatile float		ten			= 10.0f;

// Convert a floating point number to a string with a %6.3f format.  Not all
// versions of sprintf support floating point, so on the first call test to see
// if it works, and if we don't get the expected result, do the conversion in
// an alternate fashion.  This function is optimized for the range of values we
// expect to see with the power meter.
//
// On the Teensy, if you optimize for space, a smaller library is used that
// does not do have %f, %g, etc. formats.

char *
float_to_string (float number, char *buffer, int fract)
{
  static bool first_time	= true;
  static bool sprintf_works;

  if (first_time) {
    first_time = false;
    strcpy (buffer, "no results");
    sprintf (buffer, "%6.3f", ten);
    sprintf_works = (strcmp (buffer, "10.000") == 0);
    if (!sprintf_works)
      Serial.printf ("sprintf does not work for floating point, '%s'\n", buffer);
  }

  if (sprintf_works)
    sprintf (buffer, "%*.*f", fract + 3, fract, number);

  else {
    bool negative	= (number < 0.0f);
    float number2	= fabs (number);

    if (fract == 0) {
      number2 += 0.5f;
      sprintf (buffer, "%2d.", (int) (negative ? -number2 : number2));

    } else if (fract == 1) {
      int number_x10	= (int) ((number2 * 10.0f) + 0.5f);
      int number_main	= number_x10 / 10;
      int number_fract	= number_x10 % 10;

      sprintf (buffer,
	       "%2d.%c",
	       negative ? -number_main : number_main,
	       number_fract + '0');

    } else if (fract == 2) {
      int  number_x100	= (int) ((fabsf (number) * 100.0f) + 0.5f);
      int  number_main	= number_x100 / 100L;
      int  number_fract	= number_x100 % 100L;

      sprintf (buffer,
	       "%2d.%c%c",
	       negative ? -number_main : number_main,
	       ((number_fract / 10)  % 10) + '0',
	       ((number_fract / 1)   % 10) + '0');

    } else {
      int  number_x1000	= (int) ((fabsf (number) * 1000.0f) + 0.5f);
      int  number_main	= number_x1000 / 1000;
      int  number_fract	= number_x1000 % 1000;

      sprintf (buffer,
	       "%2d.%c%c%c",
	       negative ? -number_main : number_main,
	       ((number_fract / 100) % 10) + '0',
	       ((number_fract / 10)  % 10) + '0',
	       ((number_fract / 1)   % 10) + '0');
    }
  }

  return buffer;
}

And while using a suffix of 'L' will work on the Teensy 3.x/LC, it technically is not a double constant. The 'L' suffix says the constant is long double instead of double. So if you are in a context where a double is wanted, the C++ compiler will typically auto cast the long double type back to double.

It 'works' because the current ARM compilers do not have a larger long double type, and under the covers, both double and long double have the same representation.

However, not all processors have this property. The Intel/AMD x86 processors typically use the internal 80-bit type for long double.

The PowerPC compiler that I work on typically has long double being represented as a pair of double values. This is done via library routines -- there isn't hardware support directly for this paired floating point. Modern PowerPC servers (starting with power9) has native support for the IEEE 128-bit floating point that is defined in the IEEE 754R standard. One of the many tasks I have been doing over the last few years is adding the support in the GNU compiler suite to support changing the default long double format to IEEE 128-bit, and working with the GLIBC and LIBSTDC++ teams to get their support in. Most of the pieces are committed, and I have a few loose ends to get committed in January.

Just to be clear, the following is my own opinion, and not that of my employer, my cats, or anybody else. I am speculating on why Paul made the choices he did. I tend to believe it is the right choice for the Teensy LC/3.x, but there are always side effects.

That being said, Paul was faced with an impossible choice when the Teensy 3.0 came out. The AVR Arduino systems had a non-standard floating point implementation where float and double use the same format (IEEE 32-bit single precision). So code imported from these sources expected this behavior. But the ISO C/C++ standards demand that double has more precision than float typically has.

The C/C++ standards specifies that if a float and double were combined, that the float would be converted to double. In addition, a floating point constant without a 'f', 'F', 'l' or 'L' sffix is automatically a double type. If you use a normal floating point constant it will mean the expression will be done in double precision.

The Teensy 3.5/3.6 have hardware support for single precision (float) but not double precision (double). If the -fsingle-precision-constant option was not specified, it would mean you would lose a lot of the power of the machine, because it would have to do the emulation using tens, hundreds or even thousands of instructions to do basic operations.

The Teensy 4.0/4.1 has hardware support for both single and double precision. So this option is not needed on those systems.
 
To Pete ,

There is no final scetch so far , I first wanted to know if 64bit float really could be used in teensyduino , and so I know now that it can ! :)
I want to use it to do some astronomical calculations that I have now on 32 bit float on esp32 and see if the results are more accurate , which I expect them to be.
If finished I surely want to share my results.

Ronny
 
Like I said: Saint Google doesn't know everything. Thanks for sharing your knowledge.

I have been rechecking all the GPS code that I am building for work, I used a teensy 3.6 with the idea that its SDIO reader would help with the handling of the maps. However, it seems to me that teensy 4 or 4.1 are much better bases because of what is stated here regarding double variables.

At last I ventured to use Haversine's formula to calculate the distance between two points stored in the GPS memory.

4lxjfl2ucsp8oixzg.jpg


Later I will share the complete project I have ventured into with teensy, let me put the pieces of the puzzle together.

PS: You are to blame for the sleepless nights, thank you for making these fantastic MCUs come true!. I was thinking of leaving it until float ...

Happy Holidays!. Stay safe!
 
Back
Top