Minimal implementation of sprintf or printf

后端 未结 2 741
走了就别回头了
走了就别回头了 2020-12-14 08:31

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 u

相关标签:
2条回答
  • 2020-12-14 09:10

    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
    
    0 讨论(0)
  • 2020-12-14 09:25

    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
    
    0 讨论(0)
提交回复
热议问题