Go Down

Topic: Problems with <stdio.h> and sprintf for floats (Read 11 times) previous topic - next topic

cwhitto

Hi all,

Here's a code snippet that wont work on a new diecimilia


--------------------------------------------------------------------
#include <Stdio.h>

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

void loop()
{
 float temp = 3.14;
 char ascii[32];
 sprintf(ascii,"The Value is: %0.2f",temp);
 Serial.print(ascii);
 Serial.println();
 delay(2000);
}
-------------------------------------------------------------------

What I'm expecting is to get "The Value is 3.14"

What I'm actually getting is "The value is ?"

I've looked every where to find a solution but so far nada.. anyone have a tip for me?  is sprintf *broken*?

Regards C :)
Whitto

P.S.  I dont want to go the route of converting to interger etc.  this should work but I must be missing something..

mem

#1
Jan 19, 2008, 05:26 am Last Edit: Jan 19, 2008, 05:29 am by mem Reason: 1
I don't think floating point for sprintf is linked by default, see this link: http://winavr.scienceprog.com/avr-gcc-tutorial/using-sprintf--function-for-float-numbers-in-avr-gcc.html

or, if the increased code size is a problem, you could do something like this:

 char ascii[32];
 float temp = 3.14;
 int temp1 = (temp - (int)temp) * 100;
 sprintf(ascii,"The Value is: %0d.%d", (int)temp, temp1);

cwhitto

OK.

Mem,  the code snippet you provided works 100% (exactly) as I wanted mine to.

What I dont understand is *exactly* why yours works and mine doesnt..

I'm assuming that its' because sprintf (in arduino) anly works for intergers as per your original post.  If this is the case it should be mentioned in the wiki.

regards and huge thanks
Cwhitto




mem

It works because a float is not passed to sprintf. But I am not sure sprintf is officially supported at all. Did you find any reference to sprintf in the wiki?

cwhitto

Hi Mem,

The only reference to it is on this page... http://www.arduino.cc/playground/Code/LCD

where it says, (down the bottom)

"Printing Numbers

The itoa() stdlib C library function can be used to convert a number into a string, in a variety of bases (e.g. decimal, binary). The buffer must be large enough to hold the largest number, plus sign and terminating null: e.g. 32 bit base-10: "-2147483648\0" = 12 characters.

The stdlib itoa() library routine adds around 600 bytes, the roll-your-own K&R implementation, which includes string.h, adds around 670 bytes. Using snprintf() from stdio.h adds just over 2200 bytes.

This is the only reference.  But it implies that sprintf is a valid way to convert numbers to strings.
of course "numbers" means lots of things to non C++ coders,  like me........


I read this as numbers = floats.  which looking at it now, seems to be a mistake but it should be absolute.

I guess lots of new coders (me included) might assume this is a good (simple) way to convert floats to ascii.

Thing is,  lots of people here (me included) might need to take a float and output it to a LCD or other device that only takes an ascii string.  so a clear way to do it would be good.  (I'll use your code) I've collected about 5 different ways of doing it.  but your suggestion with sprintf is still the simplest.  even if it uses more code.

I think I'll do a wiki entry for this and submit it.

 

regards
Cwhitto




cwhitto

Hi Mem,

As you seem to be the *guru*.. :)

Can you suggest a variation to your code that takes into account negative numbers. (i'm thinking of a range between -10 and 50) for temp readings. which is probably typical......  

Using your previous code a value of:  temp = -0.513

would print "The value is 0.-513"

Where what we would want is "The value is -0.513"

Can you suggest another elegant solution?

I do think this should be included a an example sketch in Version 11 of the arduino software.  what do you think?

Regards
Cwhitto

 

mem

#6
Jan 19, 2008, 04:07 pm Last Edit: Jan 19, 2008, 04:22 pm by mem Reason: 1
A simple solution is
 sprintf(ascii,"The Value is: %0d.%d", (int)temp, abs(temp1));

but test it out on a range of positive and negative values before posting more widely.

Something like the following may be a better solution because it avoids sprintf (uses much less ram and no problem with buffer overflows.). These could be enhanced by including a 'precision' parameter to set the number of digits after the decimal point.

void serialPrintFloat( float f){
  Serial.print((int)f);
  Serial.print(".");
  int temp = (f - (int)f) * 100;
  Serial.println( abs(temp) );

}

void LCDPrintFloat( float f){
  lcd.printIn((int)f);
  lcd.printIn(".");
  int temp = (f - (int)f) * 100;
  lcd.printIn( abs(temp) );

cwhitto

Hi Mem,

That works fantastically.  for the ranges I've tested (-100.00 to +100.00)

Which begs the question why this is not an example sketch in the arduino environment.......
I'd think that a good (analog) floating point code example (and yours is the best I've found) is something most new users would need.

Sure you can do things with intergers *1000 etc (see below).  But a clean concise SIMPLE example like this is will do for 99% of *new* users. who may not care about the program space used by <stdio.h>.  

Thanks again
Cwhitto


here is the previous code I've been using.

Called using "print_float(tempdisp, 2);  // Number to be printed and decimal places"


---------------------------------------------------------------------------
void print_float(float f, int num_digits)
{
 int f_int;
 int pows_of_ten[4] = {
   1, 10, 100, 1000  };
 int multiplier, whole, fract, d, n;
char DispString[32];
char fraction[32];

 multiplier = pows_of_ten[num_digits];
 if (f < 0.0)
 {
   f = -f;
   Serial.print("-");
 }
 whole = (int) f;
 fract = (int) (multiplier * (f - (float)whole));

 Serial.print(whole);
 Serial.print(".");

 for (n=num_digits-1; n>=0; n--) // print each digit with no leading zero suppression
 {
   d = fract / pows_of_ten[n];
   Serial.print(d);
--------------------------------------------------------------------------------------------

This works and take up less memory program space but which is easier for new users to understand???


P.S. I'll post your code as a suggestion for inclusion in the next version of the IDE (with credit to you of course)

Hopefully these key words will help others find this post
// key words for forum search..

floating point float sprintf problem solution broken busted working elegant solution ascii character buffer atoi itoa



mem

I have no problem with you posting the code if you think it helpful but I deserve no credit  for endorsing the use of an ugly and dangerous function like sprintf  ::). Its syntax is confusing for beginners and its real easy to overflow the buffer.

I think the compound statements for serial and lcd output are a better choice for beginners.

cwhitto

Hi Mem,

When I look at your compound statement(s)
--------------------------------------------
void serialPrintFloat( float f){
  Serial.print((int)f);
  Serial.print(".");
  int temp = (f - (int)f) * 100;
  Serial.println( abs(temp) );
---------------------------------------------


This will work when you pass the float value "f" to the subroutine,  and will serial print the values correctly to the serial monitor or an lcd using the 4 wire option.

My problem is that the LCD I'm using has a I2C interface so I'm using the <wire.h> library to talk to it.

eg:

Wire.beginTransmission(0x28);  //send to LCD
Wire.send(0x80);  //init LCD
Wire.send("Current Temp = ");
Wire.send(Disp_String_buffer_ascii);
Wire.endTransmission();


I can send a "string" to the lcd,  like this
-------------------------------------
Wire.send("Current Temp = ");
---------------------------------------

Or an interger like this below,  will be interpreted as ascii (eg if int some_value = 48, I'll see "0" on the LCD)
-------------------------------------------
Wire.send(some_value);
--------------------------------------------

If I want to send some_value_as_a_string then I need to sprintf the some_value and then send the buffer to the LCD

so the compound statements dont work for me eg:

--------------------------------------------
void LCD_PrintFloat( float f){
Wire.beginTransmission(0x28);  //send to LCD
Wire.send(0x80);  //init LCD  
Wire.send((int)f);
Wire.send(".");
int temp = (f - (int)f) * 100;
Wire.send( abs(temp) );
---------------------------------------------
would not work

as far as I know I cant do compound statements.  Unless I find out otherwise I have to send "strings" to my LCD and hence I need to use sprintf.

I cant do this either (where int some_value contains 0)
-----------------------------------------------------
Wire.send("the temp is" (some_value +48));
----------------------------------------------------
This would work serial.print("the temp is" (some_value +48))

But not with Wire.send  ::(


I have to send the two things as separate ascii encoded transmissions.

Of course a serial LCD would FIX this,  or using a 4 wire connection to a standard LCD.  but I thought i'd be smart and use I2C   ::) ::)



Regards
Cwhitto


mem

#10
Jan 21, 2008, 09:09 am Last Edit: Jan 21, 2008, 09:09 am by mem Reason: 1
I am surprised that the compound statment does not work for the LCD, what does calling your LCD_PrintFloat display on the LCD?

Anyway, perhaps something like the following would work for you.

---------------------------------------
void LCD_PrintFloat( float f){
 char ascii[32];

 Wire.beginTransmission(0x28);  //send to LCD  
 itoa(int(f),ascii,10);
 strcat(ascii,".");
 int temp = (f - (int)f) * 100;
 itoa(abs(temp), &ascii[strlen(ascii)], 10);
 Wire.send(ascii);  
}

cwhitto

Hey Mem,

the compound statement I gave above was an example of one that wont work,  it wont even compile without error. so nothing goes to the LCD....Remember that this just happens to be an I2C LCD any I2C device will have the same restriction as far as I know.

I think that it's due to the <wire.h> library  which seems to only allow you to send bytes but not perform compound  functions  or perform maths,  on the same line. so.....

Wire.send (0x28) //works,  sends 28 hex
Wire.send ("aword" )  //works, sends aword as ascii to the I2C device, an LCD in my case
Wire.send (my_int) // works,  however my_int is interpreted by the LCD as ascii, The Lcd prints "0" for my_int=48

Wire.send ("aword", (myint)) // doesnt work
Wire.send ("aword", [myint]) // doesnt work
Wire.send (myint -10, DEC) // doesnt work

You get the picture...

I'll try your suggestions (when I get a chance this week).

The wire.h library syntax is documented on the wiring page.
http://wiring.org.co/reference/libraries/Wire/index.html

No compound examples are there either.  so I just dont think it can be done with <wire.h>

It's all part of the fun ;)


Thanks again
Cwhitto






mem

It looks like the wire library does not do the integer to string conversion the way the serial library does so you may need to use the LCD_PrintFloat function I posted above. I haven't tested it but it should work in principle. Remember to ensure you declare a big enough buffer, there is no runtime checking for this. Also, the function should probably be renamed LCD_sendFloat. Good luck with it.

Go Up