3

This may be a silly question, but... I tried to implement printf, but for some reason the output I get is not exactly what I expected. any idea what it could be? I would appreciate some help.

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

static int print(const char *restrict fmt, ...);
static int getfloat(float *);
static char *itoa(int, char *, int);
static void _strrev(char *);


int
main(void)
{
    float i1 = 0.0, i2 = 0.0, noi1 = 0.0, noi2 = 0.0, res = 0.0;

    print("weight - item 1: ");
    getfloat(&i1);

    print("no. of item 1: ");
    getfloat(&noi1);

    print("weight - item 2: ");
    getfloat(&i2);

    print("no. of item 2: ");
    getfloat(&noi2);

    res = ((i1 * noi1) + (i2 * noi2)) / (noi1 + noi2);
    print("%f\n", res);

    exit(EXIT_SUCCESS);
}


static int
print(const char *restrict fmt, ...)
{
    va_list ap;
    char buf[BUFSIZ] = {0}, tmp[20] = {0};
    char *str_arg;
    int i = 0, j = 0;

    va_start(ap, fmt);

    while (fmt[i] != '\0') {

        if (fmt[i] == '%') {
            i++;
            switch (fmt[i]) {
                case 'c':
                    buf[j] = (char)va_arg(ap, int);
                    j++;
                    break;

                case 'd':
                    itoa(va_arg(ap, int), tmp, 10);
                    strcpy(&buf[j], tmp);
                    j += strlen(tmp);
                    break;

                case 'f':
                    gcvt(va_arg(ap, double), 10, tmp);
                    strcpy(&buf[j], tmp);
                    j += strlen(tmp);
                    break;

                case 's':
                    str_arg = va_arg(ap, char *);
                    strcpy(&buf[j], str_arg);
                    j += strlen(str_arg);
                    break;

                default:
                    break;
            }
        } else { buf[j++ ] = fmt[i]; } 
        ++i;
    }
    fwrite(buf, j, 1, stdout);
    va_end(ap);
    return (j);
}


static int
getfloat(float *p)
{
    int c, sign = 0;
    float pwr = 0.0;


    while (c = getc(stdin), c == ' ' || c == '\t' || c == '\n')
        ;   /* ignore white spaces */
    
    sign = 1;   /* record sign */
    if (c == '+' || c == '-') {
        sign = (c == '+') ? 1 : -1;
        c = getc(stdin);
    }


    for (*p = 0.0; isdigit(c); c = getc(stdin))
        *p = 10.0 * *p + c - '0';

    if (c == '.') { c = getc(stdin); }

    for (pwr = 1.0; isdigit(c); c = getc(stdin)) {
        *p = 10.0 * *p + c - '0';
        pwr *= 10.0;
    }
    
    *p *= sign / pwr;
    if (c != EOF)
        ungetc(c, stdout);
    return (float)c;
}


static char *
itoa(int n, char *strout, int base)
{
    int i, sign;

    if ((sign = n) < 0)
        n -= n;

    i = 0;
    do {
        strout[i++] = n % base + '0';
    } while ((n /= base) != 0);

    if (sign < 0) { strout[i++] = '-'; }
    strout[i] = '\0';

    _strrev(strout);
    return (strout);
}


static void
_strrev(char *str)
{
    int i = 0, j = strlen(str) - 1;

    for ( ; i < j; ++i, --j) {
        int tmp = str[i];
        str[i] = str[j];
        str[j] = tmp;
    }
}

here is the output I get: 19.44444466

and this is the output that I expect: (or the one that I would at least like to receive, which is the one in itself that I get when I use printf)

19.444445 
3
  • 2
    19.444445 is 19.44444466 rounded. What result do you get if you ask gcvt() for 8 digits instead of 10? Or conversely, what result do you get from printf("%.8f\n", res);? Commented Mar 23, 2022 at 23:26
  • 1
    @WeatherVane the result I get when I use: printf("%.8f\n", res); is 19.44444466, now I think I more or less understand. Thank you! Commented Mar 23, 2022 at 23:55
  • 2
    tmp[20] is far too small with gcvt(-DBL_MAX, 10, tmp);. More like tmp[320]. Evan float could need maybe 50 char. Commented Mar 24, 2022 at 1:29

2 Answers 2

3
   f, F   The double argument is rounded and converted to decimal
          notation in the style [-]ddd.ddd, where the number of
          digits after the decimal-point character is equal to the
          precision specification.  If the precision is missing, it
          is taken as 6;

https://man7.org/linux/man-pages/man3/printf.3.html

The default precision for %f is six so printf() is rounding the result to six decimal places.

You'd need to play with the ndigit argument to gcvt() which is the total number of significant digits (both before and after the decimal point). You are passing in 10 so your answer has two digits before the decimal and eight after for this particular number.

Sign up to request clarification or add additional context in comments.

Comments

1

Seems that gcvt() don't do exactly what printf() do, at least with your compiler. Check it with a "real" printf with the same value.

Since you didn't gave the numbers you used for the test (avoid getfloat() and initialize directly i1, i2, noi1 and noi2 with required constants in your question), I can't run it and tell you why exactly - or if it even happens with my own compiler.

Usually, the source code for printf is at least two times bigger than yours, so you may have missed some vicious subcases. If I remember well, printf has code to decode an IEEE-754 directly and don't rely on gcvt.

1 Comment

The numbers I used for the test were, ` weight - article 1: 15 | no. of article 1: 5 | weight - item 2: 25 | no. of article 2: 4 ` and I guess I'll have to dig a bit more about gcvt(), thank you very much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.