Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Minimal implementation of sprintf or printf

Tags:

I'm working on an embedded DSP where speed is crucial, and memory is very short.

At the moment, sprintf uses the most resources of any function in my code. I only use it to format some simple text: %d, %e, %f, %s, nothing with precision or exotic manipulations.

How can I implement a basic sprintf or printf function that would be more suitable for my usage?

like image 224
Gui13 Avatar asked May 20 '13 10:05

Gui13


People also ask

Is sprintf faster than printf?

12, printf() takes 42682 cycles and sprintf() takes 38955 cycles. This is with -msmart-io=2 enabled. The speed difference is likely due to the time waiting for the I/O.

What is the difference between printf and sprintf?

The printf function formats and writes output to the standard output stream, stdout . The sprintf function formats and stores a series of characters and values in the array pointed to by buffer. Any argument list is converted and put out according to the corresponding format specification in format.

What is the purpose of sprintf () function?

The sprintf() function writes a formatted string to a variable. The arg1, arg2, ++ parameters will be inserted at percent (%) signs in the main string. This function works "step-by-step".


3 Answers

This one assumes the existence of an itoa to convert an int to character representation, and an fputs to write out a string to wherever you want it to go.

The floating point output is non-conforming in at least one respect: it makes no attempt at rounding correctly, as the standard requires, so if you have have (for example) a value of 1.234 that is internally stored as 1.2399999774, it'll be printed out as 1.2399 instead of 1.2340. This saves quite a bit of work, and remains sufficient for most typical purposes.

This also supports %c and %x in addition to the conversions you asked about, but they're pretty trivial to remove if you want to get rid of them (and doing so will obviously save a little memory).

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>

static void ftoa_fixed(char *buffer, double value);
static void ftoa_sci(char *buffer, double value);

int my_vfprintf(FILE *file, char const *fmt, va_list arg) {

    int int_temp;
    char char_temp;
    char *string_temp;
    double double_temp;

    char ch;
    int length = 0;

    char buffer[512];

    while ( ch = *fmt++) {
        if ( '%' == ch ) {
            switch (ch = *fmt++) {
                /* %% - print out a single %    */
                case '%':
                    fputc('%', file);
                    length++;
                    break;

                /* %c: print out a character    */
                case 'c':
                    char_temp = va_arg(arg, int);
                    fputc(char_temp, file);
                    length++;
                    break;

                /* %s: print out a string       */
                case 's':
                    string_temp = va_arg(arg, char *);
                    fputs(string_temp, file);
                    length += strlen(string_temp);
                    break;

                /* %d: print out an int         */
                case 'd':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 10);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                /* %x: print out an int in hex  */
                case 'x':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 16);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                case 'f':
                    double_temp = va_arg(arg, double);
                    ftoa_fixed(buffer, double_temp);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                case 'e':
                    double_temp = va_arg(arg, double);
                    ftoa_sci(buffer, double_temp);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;
            }
        }
        else {
            putc(ch, file);
            length++;
        }
    }
    return length;
}

int normalize(double *val) {
    int exponent = 0;
    double value = *val;

    while (value >= 1.0) {
        value /= 10.0;
        ++exponent;
    }

    while (value < 0.1) {
        value *= 10.0;
        --exponent;
    }
    *val = value;
    return exponent;
}

static void ftoa_fixed(char *buffer, double value) {  
    /* carry out a fixed conversion of a double value to a string, with a precision of 5 decimal digits. 
     * Values with absolute values less than 0.000001 are rounded to 0.0
     * Note: this blindly assumes that the buffer will be large enough to hold the largest possible result.
     * The largest value we expect is an IEEE 754 double precision real, with maximum magnitude of approximately
     * e+308. The C standard requires an implementation to allow a single conversion to produce up to 512 
     * characters, so that's what we really expect as the buffer size.     
     */

    int exponent = 0;
    int places = 0;
    static const int width = 4;

    if (value == 0.0) {
        buffer[0] = '0';
        buffer[1] = '\0';
        return;
    }         

    if (value < 0.0) {
        *buffer++ = '-';
        value = -value;
    }

    exponent = normalize(&value);

    while (exponent > 0) {
        int digit = value * 10;
        *buffer++ = digit + '0';
        value = value * 10 - digit;
        ++places;
        --exponent;
    }

    if (places == 0)
        *buffer++ = '0';

    *buffer++ = '.';

    while (exponent < 0 && places < width) {
        *buffer++ = '0';
        --exponent;
        ++places;
    }

    while (places < width) {
        int digit = value * 10.0;
        *buffer++ = digit + '0';
        value = value * 10.0 - digit;
        ++places;
    }
    *buffer = '\0';
}

void ftoa_sci(char *buffer, double value) {
    int exponent = 0;
    int places = 0;
    static const int width = 4;

    if (value == 0.0) {
        buffer[0] = '0';
        buffer[1] = '\0';
        return;
    }

    if (value < 0.0) {
        *buffer++ = '-';
        value = -value;
    }

    exponent = normalize(&value);

    int digit = value * 10.0;
    *buffer++ = digit + '0';
    value = value * 10.0 - digit;
    --exponent;

    *buffer++ = '.';

    for (int i = 0; i < width; i++) {
        int digit = value * 10.0;
        *buffer++ = digit + '0';
        value = value * 10.0 - digit;
    }

    *buffer++ = 'e';
    itoa(exponent, buffer, 10);
}

int my_printf(char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(stdout, fmt, arg);
    va_end(arg);
    return length;
}

int my_fprintf(FILE *file, char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(file, fmt, arg);
    va_end(arg);
    return length;
}


#ifdef TEST 

int main() {

    float floats[] = { 0.0, 1.234e-10, 1.234e+10, -1.234e-10, -1.234e-10 };

    my_printf("%s, %d, %x\n", "Some string", 1, 0x1234);

    for (int i = 0; i < sizeof(floats) / sizeof(floats[0]); i++)
        my_printf("%f, %e\n", floats[i], floats[i]);

    return 0;
}

#endif
like image 128
Jerry Coffin Avatar answered Sep 28 '22 04:09

Jerry Coffin


I wrote nanoprintf in an attempt to find a balance between tiny binary size and having good feature coverage. As of today the "bare-bones" configuration is < 800 bytes of binary code, and the "maximal" configuration including float parsing is < 2500 bytes. 100% C99 code, no external dependencies, one header file.

https://github.com/charlesnicholson/nanoprintf

I haven't seen a smaller vsnprintf implementation than this that has a comparable feature set. I also released the software in the public domain and with the Zero-clause BSD license so it's fully unencumbered.

Here's an example that uses the vsnprintf functionality:

your_project_nanoprintf.c

#define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
#define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0

// Compile nanoprintf in this translation unit.
#define NANOPRINTF_IMPLEMENTATION
#include "nanoprintf.h"

your_log.h

void your_log(char const *s);
void your_log_v(char const *fmt, ...);

your_log.c

#include "your_log.h"
#include "nanoprintf.h"

#include <stdarg.h>

void your_log_v(char const *s) {
  // Do whatever you want with the fully formatted string s.
}

void your_log(char const *fmt, ...) {
    char buf[128];

    va_arg args;
    va_start(args, fmt);
    npf_vsnprintf(buf, sizeof(buf), fmt, args); // Use nanoprintf for formatting.
    va_end(args);

    your_log_write(buf);
}

Nanoprintf also provides an snprintf-alike and a custom version that takes a user-provided putc callback for things like UART writes.

like image 36
Charles Nicholson Avatar answered Sep 28 '22 04:09

Charles Nicholson


I add here my own implementation of (v)sprintf, but it does not provide float support (it is why I am here...).

However, it implements the specifiers c, s, d, u, x and the non standard ones b and m (binary and memory hexdump); and also the flags 0, 1-9, *, +.

#include <stdarg.h>
#include <stdint.h>

#define min(a,b) __extension__\
    ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
       _a < _b ? _a : _b; })

enum flag_itoa {
    FILL_ZERO = 1,
    PUT_PLUS = 2,
    PUT_MINUS = 4,
    BASE_2 = 8,
    BASE_10 = 16,
};

static char * sitoa(char * buf, unsigned int num, int width, enum flag_itoa flags)
{
    unsigned int base;
    if (flags & BASE_2)
        base = 2;
    else if (flags & BASE_10)
        base = 10;
    else
        base = 16;

    char tmp[32];
    char *p = tmp;
    do {
        int rem = num % base;
        *p++ = (rem <= 9) ? (rem + '0') : (rem + 'a' - 0xA);
    } while ((num /= base));
    width -= p - tmp;
    char fill = (flags & FILL_ZERO)? '0' : ' ';
    while (0 <= --width) {
        *(buf++) = fill;
    }
    if (flags & PUT_MINUS)
        *(buf++) = '-';
    else if (flags & PUT_PLUS)
        *(buf++) = '+';
    do
        *(buf++) = *(--p);
    while (tmp < p);
    return buf;
}

int my_vsprintf(char * buf, const char * fmt, va_list va)
{
    char c;
    const char *save = buf;

    while ((c  = *fmt++)) {
        int width = 0;
        enum flag_itoa flags = 0;
        if (c != '%') {
            *(buf++) = c;
            continue;
        }
    redo_spec:
        c  = *fmt++;
        switch (c) {
        case '%':
            *(buf++) = c;
            break;
        case 'c':;
            *(buf++) = va_arg(va, int);
            break;
        case 'd':;
            int num = va_arg(va, int);
            if (num < 0) {
                num = -num;
                flags |= PUT_MINUS;
            }
            buf = sitoa(buf, num, width, flags | BASE_10);
            break;
        case 'u':
            buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_10);
            break;
        case 'x':
            buf = sitoa(buf, va_arg(va, unsigned int), width, flags);
            break;
        case 'b':
            buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_2);
            break;
        case 's':;
            const char *p  = va_arg(va, const char *);
            if (p) {
                while (*p)
                    *(buf++) = *(p++);
            }
            break;
        case 'm':;
            const uint8_t *m  = va_arg(va, const uint8_t *);
            width = min(width, 64); // buffer limited to 256!
            if (m)
                for (;;) {
                    buf = sitoa(buf, *(m++), 2, FILL_ZERO);
                    if (--width <= 0)
                        break;
                    *(buf++) = ':';
                }
            break;
        case '0':
            if (!width)
                flags |= FILL_ZERO;
            // fall through
        case '1'...'9':
            width = width * 10 + c - '0';
            goto redo_spec;
        case '*':
            width = va_arg(va, unsigned int);
            goto redo_spec;
        case '+':
            flags |= PUT_PLUS;
            goto redo_spec;
        case '\0':
        default:
            *(buf++) = '?';
        }
        width = 0;
    }
    *buf = '\0';
    return buf - save;
}

int my_sprintf(char * buf, const char * fmt, ...)
{
    va_list va;
    va_start(va,fmt);
    int ret = my_vsprintf(buf, fmt, va);
    va_end(va);
    return ret;
}

#if TEST
int main(int argc, char *argv[])
{
    char b[256], *p = b;
    my_sprintf(b, "%x %d %b\n", 123, 123, 123);
    while (*p)
        putchar(*p++);
}
#endif
like image 35
calandoa Avatar answered Sep 28 '22 05:09

calandoa