printing a double variable

How can I print a double precision variable to the serial port?

With something like

double x;
double y;
double z;

x = 10;
y = 3.1;
z = x / y;

serial.println (z);

It displays "3".

Also, I used millis to time the "z = x / y;" line and it seems to take about 1/10th of a millisecond. Is it really doing 64 bit floating point division in a fraction of a millisecond? I was expecting something in the 10ms or more range.

1 Like

Serial.print does not print floating point. Here is a sketch with a function you can use to display the number of decimal places you want.

double x; 
double y; 
double z; 
 
void printDouble( double val, unsigned int precision){
// prints val with number of decimal places determine by precision
// NOTE: precision is 1 followed by the number of zeros for the desired number of decimial places
// example: printDouble( 3.1415, 100); // prints 3.14 (two decimal places)

    Serial.print (int(val));  //prints the int part
    Serial.print("."); // print the decimal point
    unsigned int frac;
    if(val >= 0)
        frac = (val - int(val)) * precision;
    else
        frac = (int(val)- val ) * precision;
    Serial.println(frac,DEC) ;
} 
 
void  setup(){
  Serial.begin(9600);
  Serial.println("Print floating point example");   
  printDouble( 3.1415, 100);  // example of call to printDouble to print pi to two decimal places
  x = 10; 
  y = 3.1; 
  z = x / y;   
}
 
 
 void loop(){
   printDouble(z,10);   // one decimal place
   printDouble(z,100);  // two decimal places
   printDouble(z,1000); // three decimal places
   z = z + .1;
   delay(100);
 }
1 Like

Thanks. I'll try it out when I get home.

1 Like

printDouble( 1.06, 100 ); ----------------> prints 1.5

Any idea how one would zero pad that to get the correct value? even if it came out 1.05, that'd be ok.
Thanks

1 Like

Try the modified code here, I have not had a chance to test it so please let me if it works or not.

void printDouble( double val, unsigned int precision){
// prints val with number of decimal places determine by precision
// NOTE: precision is 1 followed by the number of zeros for the desired number of decimial places
// example: printDouble( 3.1415, 100); // prints 3.14 (two decimal places)

    Serial.print (int(val));  //prints the int part
    Serial.print("."); // print the decimal point
    unsigned int frac;
    if(val >= 0)
      frac = (val - int(val)) * precision;
    else
       frac = (int(val)- val ) * precision;
    int frac1 = frac;
    while( frac1 /= 10 )
        precision /= 10;
    precision /= 10;
    while(  precision /= 10)
        Serial.print("0");

    Serial.println(frac,DEC) ;
}

When I get a chance, I will change the function so the second argument is the actual number of decimal places (not 1 with the number of zeros indicating decimal places) to make the fix simpler. But I hope this will get you going

1 Like

works great!
thanks again

1 Like

Good to hear it works. Here is an improved version, it takes the number of decimal places as the second argument. It also handles the full precision of a double on the Arduino ( up to 6 decimal places).

A cr/lf is no longer sent, so that multiple prints can be done on one line, free to add a Serial.println() at the end if you want.

void printDouble( double val, byte precision){
  // prints val with number of decimal places determine by precision
  // precision is a number from 0 to 6 indicating the desired decimial places
  // example: printDouble( 3.1415, 2); // prints 3.14 (two decimal places)

  Serial.print (int(val));  //prints the int part
  if( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
       mult *=10;
       
    if(val >= 0)
      frac = (val - int(val)) * mult;
    else
      frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
      padding--;
    while(  padding--)
      Serial.print("0");
    Serial.print(frac,DEC) ;
  }
}

Unfortunatly, no opportunity to test this myself tonight so any feedback is welcome.

1 Like

also works great!
thank you thank you

1 Like

thanks for sharing

1 Like

mem,

How might you rewrite the "printDouble" function to return a string instead of printing to the serial port?

I need to create a string of comma separated data, and some of the data contains floats that i want to format. i want to create the whole string then check the length before i send it to the serial port.

i was thinking of using sprintf for this but then found out that it doesn't support floats.
so i was thinking, instead of passing in a float to sprintf i would pass in a string of a float that was already formatted by a new version your "printDouble" function.

Thanks
Jeff

1 Like

Hi jeff,

printDouble uses the print formatting routines in the Serial library to do the conversion from number to characters, so I don't think there is an easy way to do what you want without messing with the low level formatting routines used by print.

One idea that comes to mind is to do a very simple version of printf for output to the serial port that handles arguments for strings, integers and floats but does not have all the fancy flags in a full printf implementation. Even a much simplified printf isn't easy, but I will see if I can come up with a quick way of putting something together. Don't count on it though :wink:

1 Like

How might you rewrite the "printDouble" function to return a string instead of printing to the serial port?

Here is some code (derived from that posted by mem) to produce a string in a buffer corresponding to a floating point value. The helper function fmtUnsigned() will produce a string in a buffer corresponding to an unsigned integral value with optional leading zero padding.

void fmtDouble(double val, byte precision, char *buf, unsigned bufLen = 0xffff);
unsigned fmtUnsigned(unsigned long val, char *buf, unsigned bufLen = 0xffff, byte width = 0);

//
// Produce a formatted string in a buffer corresponding to the value provided.
// If the 'width' parameter is non-zero, the value will be padded with leading
// zeroes to achieve the specified width.  The number of characters added to
// the buffer (not including the null termination) is returned.
//
unsigned
fmtUnsigned(unsigned long val, char *buf, unsigned bufLen, byte width)
{
  if (!buf || !bufLen)
    return(0);

  // produce the digit string (backwards in the digit buffer)
  char dbuf[10];
  unsigned idx = 0;
  while (idx < sizeof(dbuf))
  {
    dbuf[idx++] = (val % 10) + '0';
    if ((val /= 10) == 0)
      break;
  }

  // copy the optional leading zeroes and digits to the target buffer
  unsigned len = 0;
  byte padding = (width > idx) ? width - idx : 0;
  char c = '0';
  while ((--bufLen > 0) && (idx || padding))
  {
    if (padding)
      padding--;
    else
      c = dbuf[--idx];
    *buf++ = c;
    len++;
  }

  // add the null termination
  *buf = '\0';
  return(len);
}

//
// Format a floating point value with number of decimal places.
// The 'precision' parameter is a number from 0 to 6 indicating the desired decimal places.
// The 'buf' parameter points to a buffer to receive the formatted string.  This must be
// sufficiently large to contain the resulting string.  The buffer's length may be
// optionally specified.  If it is given, the maximum length of the generated string
// will be one less than the specified value.
//
// example: fmtDouble(3.1415, 2, buf); // produces 3.14 (two decimal places)
//
void
fmtDouble(double val, byte precision, char *buf, unsigned bufLen)
{
  if (!buf || !bufLen)
    return;

  // limit the precision to the maximum allowed value
  const byte maxPrecision = 6;
  if (precision > maxPrecision)
    precision = maxPrecision;

  if (--bufLen > 0)
  {
    // check for a negative value
    if (val < 0.0)
    {
      val = -val;
      *buf = '-';
      bufLen--;
    }

    // compute the rounding factor and fractional multiplier
    double roundingFactor = 0.5;
    unsigned long mult = 1;
    for (byte i = 0; i < precision; i++)
    {
      roundingFactor /= 10.0;
      mult *= 10;
    }

    if (bufLen > 0)
    {
      // apply the rounding factor
      val += roundingFactor;

      // add the integral portion to the buffer
      unsigned len = fmtUnsigned((unsigned long)val, buf, bufLen);
      buf += len;
      bufLen -= len;
    }

    // handle the fractional portion
    if ((precision > 0) && (bufLen > 0))
    {
      *buf++ = '.';
      if (--bufLen > 0)
        buf += fmtUnsigned((unsigned long)((val - (unsigned long)val) * mult), buf, bufLen, precision);
    }
  }

  // null-terminate the string
  *buf = '\0';
}
1 Like

Hi Don, nicely done ! :slight_smile:

1 Like

Latest code for serial port and version 0012 text lcd library. This code has a fix to correctly print the sign of small negative numbers

Serial version:

 void printDouble( double val, byte precision){
  // prints val with number of decimal places determine by precision
  // precision is a number from 0 to 6 indicating the desired decimial places
  // example: lcdPrintDouble( 3.1415, 2); // prints 3.14 (two decimal places)
 
  if(val < 0.0){
    Serial.print('-');
    val = -val;
  }

  Serial.print (int(val));  //prints the int part
  if( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
  mult *=10;
 
    if(val >= 0)
 frac = (val - int(val)) * mult;
    else
 frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
 padding--;
    while(  padding--)
 Serial.print("0");
    Serial.print(frac,DEC) ;
  }
}

version for the Arduino 0012 liquid crystal library (change print to printIn for the older lcd library)

 void lcdPrintDouble( double val, byte precision){
  // prints val on a ver 0012 text lcd with number of decimal places determine by precision
  // precision is a number from 0 to 6 indicating the desired decimial places
  // example: printDouble( 3.1415, 2); // prints 3.14 (two decimal places)
 
  if(val < 0.0){
    lcd.print('-');
    val = -val;
  }

  lcd.print (int(val));  //prints the int part
  if( precision > 0) {
    lcd.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
  mult *=10;
 
    if(val >= 0)
 frac = (val - int(val)) * mult;
    else
 frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
 padding--;
    while(  padding--)
 lcd.print("0");
    lcd.print(frac,DEC) ;
  }
}
1 Like

Dear Mem,

The routine for printing decimals in an LCD display you posted, didn´t work for me.

The double definition of variables doesn´t show in my manual, only :

Int
byte
long
float
array

Besides, I have never progam in C before, only in BASIC.
So, I don´t know where should I put your routine: all instructions together, a part at the beginning, etc.

Consider I am using a display called GDM1602K Xiamen Ocular, using LCD4bit driver.

Hope you can help me a little more....

Any ideas?

1 Like

double and float are the same in the arduino environment. The routine works fine for me, here is the test sketch I used with the 0012 liquidcrystal library:

#include <LiquidCrystal.h>

// LiquidCrystal display with:
// rs on pin 7        (LCD pin 4 ) aka DI
// rw on pin 6        (LCD pin 5)
// enable on pin 5 (LCD pin 6)
// d4, d5, d6, d7 on pins 9, 10, 11, 12  (LCD pins 11-14)

LiquidCrystal lcd(7, 6, 5, 9, 10, 11, 12);

void setup()
{
  // Print a message to the LCD.
  lcd.print("float test!");
  delay(2000);
  lcd.clear();  
}

float f = 3.1415; // a floating point test value
double d = 0.123456; // a double test value

void loop()
{

   lcd.setCursor(0, 0) ;
   lcdPrintFloat(f,4  ); // show value of f with 4 digits after decimal point
   lcd.setCursor(0,1) ;
   lcdPrintFloat(d,6);   // show value of d with 6 digits after decimal point
   delay(2000);
   f -= 1.0;  // subtract 1 from the value to print on the next loop
   d +=.123456;   // add 0.123456 to the value and print on the next loop
}

void lcdPrintFloat( float val, byte precision){
  // prints val on a ver 0012 text lcd with number of decimal places determine by precision
  // precision is a number from 0 to 6 indicating the desired decimial places
  // example: lcdPrintFloat( 3.1415, 2); // prints 3.14 (two decimal places)

  if(val < 0.0){
    lcd.print('-');
    val = -val;
  }

  lcd.print ((long)val);  //prints the integral part
  if( precision > 0) {
    lcd.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
  mult *=10;

    if(val >= 0)
 frac = (val - int(val)) * mult;
    else
 frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;
    while( frac1 /= 10 )
 padding--;
    while(  padding--)
 lcd.print("0");
    lcd.print(frac,DEC) ;
  }
}

edit: code updated, function renamed

Note that my LCD panel is wired differently from the Arduino example sketch. Make sure your sketch pins match the wiring you have used to connect the panel

if you use the old lcd4bit library you will need to modify the syntax. it may be easier switching to the new arduino library

1 Like

Hi
this is the code I use to print a double to either LCD or monitor.(I can't take credit for it, i just modified it to fit my needs) If you search arduino home tempToAscii you will find the original author. Sorry I don't remember who. But thanks a lot.

The good thing about this function is that it returns an ascii string.

char* tempToAscii(float temp)
// convert long to type char and store in variable array ascii
{
  char ascii[20];// needs to be this big to hold a type float

  int frac;
  int rnd;

    rnd = (unsigned int)(temp*1000)%10;
    frac=(unsigned int)(temp*100)%100;  //get three numbers to the right of the deciaml point.
    if (rnd>=5) frac=frac+1;
    itoa((int)temp,ascii,10);           // I changed it to 2 decimal places
    strcat(ascii,".");

  if (frac<10)  {itoa(0,&ascii[strlen(ascii)],10); }   // if fract < 10 should print .0 fract ie if fract=6 print .06

    itoa(frac,&ascii[strlen(ascii)],10); //put the frac after the deciaml
    
  return ascii;
}

Further code to show how I use it

lcd.print(tempToAscii(tempC))

or

1 Like

sorry about double posting I pressed the wrong button before I had finished. :-[

I use this code to display temperature readings from two DS18B20 sensors on a LCD. That's why it is only to 2 decimal places.
It could easily be modified to however many places you want.
What I like about this function is that you only have characters to play with and they are the easiest to print

cheers

1 Like

Hi Shepparton, there are a few disadvantages in using the code in reply#16. It requires some RAM permanently reserved for the conversion routine (BTW, there is a bug in the code, char ascii[20] needs to be static otherwise the value may not be preserved when the calling function accesses the return value). It also hard codes the number of decimal places.

For applications that need to have the floating point value returned as a string, I think the code posted by Don Kinzer in reply #11 in this thread would be a better choice.

But for displaying floating point on an LCD or serial terminal, the code posted in reply#13 makes the most efficient use RAM.

I have decided to rename the function I posted to lcdPrintFloat and have edited the code in reply#15 accordingly. It will still work with double values. This thread started with a question about how to display a double value on the serial terminal but I think most people looking for a solution are thinking about float, so I hope the rename will be helpful.

1 Like

Thanks Mem.
I'm still new to C and micros with limited memory and how it is used so any advice is gratefully accepted.
Just one question (although this may not be the correct thread) would it be possible to have a repository for all useful functions in one place? Perhaps in the playground where members could be invited to post their code?
I know that for someone like me who is still learning it would be of immense value.
cheers

1 Like