32

Guys, I want to know if float variables can be used in sprintf() function.

Like, if we write:

sprintf(str,"adc_read = %d \n",adc_read);

where adc_read is an integer variable, it will store the string

"adc_read = 1023 \n"

in str (assuming that adc_read = 1023)

How can I use a float variable in place of integer?

starblue
  • 55,348
  • 14
  • 97
  • 151
aditya
  • 952
  • 3
  • 16
  • 33
  • What C-library are you linking against? – sybreon May 25 '09 at 09:03
  • 1
    Note you are trying to format a double. With varargs the compiler will automatically promote a float value to double. – Richard May 25 '09 at 09:07
  • 6
    "Embedded", guys. That's the key. It doesn't matter what your fancy-shmancy libraries do on your machines with GB of memory :-) Embedded platforms generally have trade-offs aiming towards minimal memory footprint. – paxdiablo May 25 '09 at 09:21
  • 1
    well, i'm usin avr-gcc compiler, and avr-libc library – aditya May 25 '09 at 09:40

13 Answers13

59

Since you're on an embedded platform, it's quite possible that you don't have the full range of capabilities from the printf()-style functions.

Assuming you have floats at all (still not necessarily a given for embedded stuff), you can emulate it with something like:

char str[100];
float adc_read = 678.0123;

char *tmpSign = (adc_read < 0) ? "-" : "";
float tmpVal = (adc_read < 0) ? -adc_read : adc_read;

int tmpInt1 = tmpVal;                  // Get the integer (678).
float tmpFrac = tmpVal - tmpInt1;      // Get fraction (0.0123).
int tmpInt2 = trunc(tmpFrac * 10000);  // Turn into integer (123).

// Print as parts, note that you need 0-padding for fractional bit.

sprintf (str, "adc_read = %s%d.%04d\n", tmpSign, tmpInt1, tmpInt2);

You'll need to restrict how many characters come after the decimal based on the sizes of your integers. For example, with a 16-bit signed integer, you're limited to four digits (9,999 is the largest power-of-ten-minus-one that can be represented).

However, there are ways to handle this by further processing the fractional part, shifting it by four decimal digits each time (and using/subtracting the integer part) until you have the precision you desire.


Update:

One final point you mentioned that you were using avr-gcc in a response to one of the other answers. I found the following web page that seems to describe what you need to do to use %f in your printf() statements here.

As I originally suspected, you need to do some extra legwork to get floating point support. This is because embedded stuff rarely needs floating point (at least none of the stuff I've ever done). It involves setting extra parameters in your makefile and linking with extra libraries.

However, that's likely to increase your code size quite a bit due to the need to handle general output formats. If you can restrict your float outputs to 4 decimal places or less, I'd suggest turning my code into a function and just using that - it's likely to take up far less room.

In case that link ever disappears, what you have to do is ensure that your gcc command has "-Wl,-u,vfprintf -lprintf_flt -lm". This translates to:

  • force vfprintf to be initially undefined (so that the linker has to resolve it).
  • specify the floating point printf() library for searching.
  • specify the math library for searching.
Daryl Wright
  • 81
  • 1
  • 8
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • it's giving compilation error error: expected expression before ‘int’ in above code in line 5. – aditya May 25 '09 at 09:34
  • @aditya, have a look at the comments, they now show how the numbers are extracted. – paxdiablo May 25 '09 at 11:04
  • Note that manipulating a float by subtracting out the integer portion and multiplying by 10000 to get the fraction part may change the value of some of the least significant digits – Trent Dec 31 '09 at 04:55
  • 2
    for gcc / stm32, adding this worked for me:LDFLAGS += -u _printf_float – user1867382 Dec 05 '19 at 20:02
8

Isn't something like this really easier:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char str[10];
float adc_read = 678.0123;

dtostrf( adc_read, 3, 4, temp );
sprintf(str,"adc_read = %10s \n", temp);
printf(temp);
crharper
  • 81
  • 1
  • 1
2

Yes you can. However, it depends on the C-library that you are linking against and you need to be aware of the consequences.

Since you are programming for embedded applications, realise that floating-point support is emulated for a lot of embedded architectures. Compiling in this floating-point support will end up increasing the size of your executable significantly.

sybreon
  • 3,128
  • 18
  • 19
1

Many embedded systems have a limited snprintf function that doesn't handle floats. I wrote this, and it does the trick fairly efficiently. I chose to use 64-bit unsigned integers to be able to handle large floats, so feel free to reduce them down to 16-bit or whatever needs you may have with limited resources.

#include <stdio.h>   // for uint64_t support.


int  snprintf_fp( char destination[], size_t available_chars, int decimal_digits,
                  char tail[], float source_number )
{
    int   chars_used  = 0;    // This will be returned.


    if ( available_chars > 0 )
    {
        // Handle a negative sign.
        if ( source_number < 0 )
        {
            // Make it positive
            source_number = 0 - source_number;
            destination[ 0 ] = '-';
            ++chars_used;
        }

        // Handle rounding
        uint64_t zeros = 1;
        for ( int i = decimal_digits; i > 0; --i )
            zeros *= 10;

        uint64_t source_num = (uint64_t)( ( source_number * (float)zeros ) + 0.5f );

        // Determine sliding divider max position.
        uint64_t  div_amount = zeros;       // Give it a head start
        while ( ( div_amount * 10 ) <= source_num )
            div_amount *= 10;

        // Process the digits
        while ( div_amount > 0 )
        {
            uint64_t whole_number = source_num / div_amount;
            if ( chars_used < (int)available_chars )
            {
                destination[ chars_used ] = '0' + (char)whole_number;
                ++chars_used;

                if ( ( div_amount == zeros ) && ( zeros > 1 ) )
                {
                    destination[ chars_used ] = '.';
                    ++chars_used;
                }
            }
            source_num -= ( whole_number * div_amount );
            div_amount /= 10;
        }


        // Store the zero.
        destination[ chars_used ] = 0;

        // See if a tail was specified.
        size_t tail_len = strlen( tail );

        if ( ( tail_len > 0 ) && ( tail_len + chars_used < available_chars ) )
        {
            for ( size_t i = 0; i <= tail_len; ++i )
                destination[ chars_used + i ] = tail[ i ];
            chars_used += tail_len;
        }
    }

    return chars_used;
}

main()
{
    #define TEMP_BUFFER_SIZE 30
    char temp_buffer[ TEMP_BUFFER_SIZE ];
    char  degrees_c[] = { (char)248, 'C', 0 };
    float  float_temperature = 26.845f;

    int len = snprintf_fp( temp_buffer, TEMP_BUFFER_SIZE, 2, degrees_c, float_temperature );
}
DirkDi99ler
  • 261
  • 3
  • 3
  • Even implementations that can support a full-featured printf can generally omit it, saving a *huge* amount of code space, if it's not required. Unless a program needs to output numbers in exponential-format, it will usually be better off computing a `uint64_t` value that's some power of ten times the correct value, converting that to a string, and inserting a decimal point. – supercat Apr 11 '19 at 16:08
  • Sure, but converting an existing project that uses floats throughout would be too much needed surgery. Even multiplying the float by (for example) 100, then type casting that to an integer requires some rounding, negative sign handling, a loop to process digits before the decimal, storing the decimal, storing additional chars, leading zeros, post zeros, and adding a suffix is still a fair amount of code - all of which is handled in my routine that works with actual floats. – DirkDi99ler May 01 '19 at 16:06
  • I simply intended that you might mention that techniques such as yours aren't useful only on implementations that lack a full-featured printf, but also on those where code space is limited. – supercat May 01 '19 at 17:51
1

use the %f modifier:

sprintf (str, "adc_read = %f\n", adc_read);

For instance:

#include <stdio.h>

int main (void) 
{
    float x = 2.5;
    char y[200];

    sprintf(y, "x = %f\n", x);
    printf(y);
    return 0;
}

Yields this:

x = 2.500000

Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319
1

Yes of course, there is nothing special with floats. You can use the format strings as you use in printf() for floats and anyother datatypes.

EDIT I tried this sample code:

float x = 0.61;
char buf[10];
sprintf(buf, "Test=%.2f", x);
printf(buf);

Output was : Test=0.61

Naveen
  • 74,600
  • 47
  • 176
  • 233
  • 3
    I did, but it didn't work it prints a ? character instead of the string – aditya May 25 '09 at 08:54
  • See the edit for my sample code.. I have a feeling that your buffer "str" is too small..try using something similar %.2f to set the precision. – Naveen May 25 '09 at 09:02
  • size of my buffer is 100 actually, i'm printing it on serial port, so i think problem is in transmitting float variables over UART – aditya May 25 '09 at 09:13
  • 2
    @aditya, sprintf (if available) will generate standard ASCII so it's not a problem with the output. More likely it's a limitation of your embedded platform. – paxdiablo May 25 '09 at 09:17
1

Don't expect sprintf (or any other function with varargs) to automatically cast anything. The compiler doesn't try to read the format string and do the cast for you; at runtime, sprintf has no meta-information available to determine what is on the stack; it just pops bytes and interprets them as given by the format string. sprintf(myvar, "%0", 0); immediately segfaults.

So: The format strings and the other arguments must match!

Erich Kitzmueller
  • 36,381
  • 5
  • 80
  • 102
0

%g can do this:

#include <stdio.h>
int main() {
  float w = 234.567;
  char x[__SIZEOF_FLOAT__];
  sprintf(x, "%g", w);
  puts(x);
}
0

Similar to paxdiablo above. This code, inserted in a wider app, works fine with STM32 NUCLEO-F446RE.

#include <stdio.h>
#include <math.h>
#include <string.h>
void IntegFract(char *pcIntegStr, char *pcFractStr, double dbValue, int iPrecis);

main()
{
   char acIntegStr[9], acFractStr[9], char counter_buff[30];
   double seconds_passed = 123.0567;
   IntegFract(acIntegStr, acFractStr, seconds_passed, 3);
   sprintf(counter_buff, "Time: %s.%s Sec", acIntegStr, acFractStr);
}

void IntegFract(char *pcIntegStr, char *pcFractStr, double dbValue, int 
iPrecis)
{
    int iIntegValue = dbValue;
    int iFractValue = (dbValue - iIntegValue) * pow(10, iPrecis);
    itoa(iIntegValue, pcIntegStr, 10);
    itoa(iFractValue, pcFractStr, 10);
    size_t length = strlen(pcFractStr);
    char acTemp[9] = "";
    while (length < iPrecis)
    {
        strcat(acTemp, "0");
        length++;
    }
    strcat(acTemp, pcFractStr);
    strcpy(pcFractStr, acTemp);
}

counter_buff would contain 123.056 .

Occasional
  • 11
  • 3
0

Yes, and no. Despite what some other replies have said, the C compiler is required to perform conversions for sprintf(), and all other variadic functions, as follows:

  • char => int
  • short => int
  • float => double

(and signed/unsigned variants of the above integral types)

It does this precisely because sprintf() (and the other print()-family functions) would be unusable without it. (Of course, they're pretty unusable as it is.)

But you cannot assume any other conversions, and your code will have undefined behaviour - read: crash! - if you do it.

dcw
  • 3,481
  • 2
  • 22
  • 30
  • 1
    The compiler is required to perform _conversions_. A _cast_ is explicitly requested by the programmer. – mlp Jun 06 '09 at 08:50
  • 1
    "Unusable" is a pretty strong word. The printf() family is quite usable, but it is not perfect. It has obvious type-safety flaws, but the compiler can easily check the types of the arguments you're passing in 99% of use cases and give you a warning/error (see gcc's -Wformat et al). – Adam Rosenfield Jun 07 '09 at 14:14
  • Adam: "99%" is also a pretty emphatic claim, =P. _Some_ compilers can do that check, but are only able to do so when the format parameter is a string literal included in the printf() statement. If the format string is obtained from elsewhere, or synthesised in situ, then the compiler is of no help. – dcw Jun 08 '09 at 03:51
  • @AdamRosenfield: A major tragedy IMHO is that ANSI didn't allow variadic functions to specify a floating-point parameter type and have all floating-point arguments coerced to those. A clean way to for the language to handle floating-point types would have been to define `long double` as the type produced by multiplying two `double` values, and `long float` as the type produced by multiplying to `float` values (which might be the same as `float` or `long double`, or might be a form optimized for computation). A major purpose behind the IEEE-754 extended-precision type was... – supercat Jun 09 '15 at 22:50
  • ...that having a 16-bit and 32-bit processor unpack 64-bit double-precision values to a larger 80-bit form, perform a sequence of operations on them, and then pack the result, would be faster than having to perform the unpacking and repacking operations after each operation. For that to work, the result of multiplying two `double` values should be a `long double`. Having it pretend to be a `double` yields muddled floating-point semantics. – supercat Jun 09 '15 at 22:56
0

Look in the documentation for sprintf for your platform. Its usually %f or %e. The only place you will find a definite answer is the documentation... if its undocumented all you can do then is contact the supplier.

What platform is it? Someone might already know where the docs are... :)

jheriko
  • 3,043
  • 1
  • 21
  • 28
  • it's Atmega128 microcontroller The compiler is avr-gcc. Thanks for the reply, but the answer given by Pax worked perfectly. – aditya May 25 '09 at 10:43
-1

Don't do this; integers in C/C++ are always rounded down so there is no need to use the floor function.

char str[100]; 
int d1 = value;

Better to use

int d1 = (int)(floor(value));

Then you won't get rounding up of the integer part (68.9999999999999999 becomes 69.00..). 68.09999847 instead of 68.1 is difficult to avoid - any floating point format has limited precision.

Scott Baker
  • 10,013
  • 17
  • 56
  • 102
setltd
  • 1
-2

Yes, use %f

rein
  • 32,967
  • 23
  • 82
  • 106