3

considering this function

double avg(double v1,double v2,...)
{
    double sum=v1+v2;
    int counter=2;
    double temp;
    va_list pargs;
    va_start(pargs,v2);
    while((temp=va_arg(pargs,double))!=0.0)
    {
        sum+=temp;
        counter++;
    }
    va_end(pargs);
    return sum/counter;
}

This call printf("%lf\n",avg(3.0,4.5,4.5,3.0,0.0)) returns the correct result, but if I delete the last parameter 0.0 it prints -321738127312000000000.0000000, but sum and counter have the right values. I kinda don't understand why I have to check that !=0.0 and have the last param 0.0

6
  • People that know stuffs told me it's bad to compare floating point values using == operator. I'm a floating point n00b, so i'm not sure about it, but it looks like bad practice Commented May 12, 2009 at 18:15
  • @litb: It is bad (TM). Trust me, without a proper epsilon, you wouldn't get a submission accepted on some of those ACM problems. Real life, is much harder -- I am yet to see a naked == on floating points yet in production code. Commented May 12, 2009 at 18:23
  • 3
    @litb: you shouldn't compare floating point numbers that result from calculations using the equality operator. Literal values, especially values that can be exactly represented, are generally accepted as safe. Commented May 12, 2009 at 18:27
  • @D.Shawley, right on point, as in this case where the literal 0.0 is used to flag the end of the varags list. But is 0.0 a good flag value for such a list? I'm not so sure. Commented May 12, 2009 at 22:38
  • Comparing 0.0 to 0.0 is completely safe. Just remember that -0.0 != 0.0. dirkgently is right that you need epsilons on any calculation result, but that's not applicable here. Commented May 13, 2009 at 7:25

5 Answers 5

6

Because without any other external information, the function has no idea how many arguments were passed in. There are several strategies to solve this problem: include an explicit argument which is the number of extra arguments, use a format string to define the arguments (such as with the printf and scanf family of functions), or use a sentinel value such as 0 to declare the end of the arguments.

In your case, if you omit the sentinel, the function just keeps walking down the stack until it hits a zero value, and depending on what data is on the stack, you could get wildly different results, all incorrect.

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

Comments

3

if you remove != 0.0 your program does dirty read until it reads a zero'ed memory block.

you have two choices:

  • specify how many arguments you are passing i.e. avg(3, 4.3, 2.0, 3.0);
  • specify a terminator or sentinel i.e. avg(4.3, 2.0, 3.0, 0.0);

EDIT

for the sake of curiosity I tried to alleviate the need of an esplicit terminator using variadic macros:

#define avg(v1, v2, ...) _avg((v1), (v2), __VA_ARGS__, 0.0)

double _avg(double v1,double v2,...) 
{ 
    /* same code, just prefixing function name with _ */

beware:

avg(3.0, 3.0, 0.0, 100.0, 100.0) 

yields 3.0, since you are terminating the va_list prematurely. You can try use another "weird" sentinel value...

2 Comments

Another macro way using C99's compound literals: #define avg(...) _argv(sizeof((double[]){VA_ARGS})/sizeof(double), (double[]){VA_ARGS})
it might be a good idea to use NaN or infinity as sentinel value (e.g. NAN from <math.h>)
1

Ultimately this has to do with how arguments are passed to functions. On the stack the arguments are just all loaded up in order, but the function has no way of knowing when it's done reading arguments. There's still data on the stack, though.

That's what the test for != 0.0 does, it uses a sentinel value (0) to identify the end of the series. Another way to do this is to pass the number of items in as the first parameter to the function, and then use a for loop to loop over the variable args.

Comments

1

You need to have a guard value (0.0) and check for it, because the compiler does not necessarily count or delimit parameters when it constructs a stack frame. Therefore you can continue reading (or writing) beyond the list of parameters and into data that holds your return pointer, your local variables, or just about anything else. If you look at your compiler's implementation of va_arg, you will probably find that all it is doing is initializing a pointer just beyond the address of the your variable (v2) and then incrementing it by the size you specify (double). It will happily do this until you get a read violation.

Comments

0

As everyone has mentioned your code relies on a sentinel value to know when it has reached the end of the list. I personally think it is inappropriate to use a sentinel for a function such as avg(). I would change the function to explicitly state the number of arguments as the first argument (as dfa has suggested).

It's only OK to use a sentinel value if your domain has a value that is appropriate to use. For example if you are dealing with only positive numbers, then you could use any negative number as the sentinel. However, it makes more sense for avg() to accept the entire domain of floats.

Comments

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.