WeBWorK Main Forum

num_cmp Mystery

num_cmp Mystery

by Andrew Dabrowski -
Number of replies: 7

I'm puzzled by the behavior of num_cmp in a certain context.  Here's a MWE:

DOCUMENT();        # This should be the first executable line in the problem.


Context() -> variables -> are(t => 'Real');

$df= Formula("500 *pi/6*cos((pi/6)*t)") -> reduce;

    When \(t=3\),
    \(\{$df->TeX\}=\) \{ans_rule(20)\}

ANS(num_cmp($df->eval(t=> 3)));


The correct answer is pi/6*500*cos(pi/2) = 0, and webwork accepts either as correct.

However 500*cos(pi/2) is marked incorrect, although it is also equal to 0.

I thought at first this might be due to WW evaluating cos(pi/2) numerically and not getting exactly 0.

However that doesn't fly because because 500*cos(pi/2) would be closer to pi/6*500*cos(pi/2) than is 0, but only 0 is accepted as correct.

What's going on here?

In reply to Andrew Dabrowski

Re: num_cmp Mystery

by Alex Jordan -
It could be due to rounding error. Neither thing is really 0, since pi is not really pi.

If I do:


Then I see:

It does seem strange that the zero level would be in between those numbers, with the first counting as equivalent to 0 and the second one not. But I'm not familiar with how num_cmp works.

In reply to Alex Jordan

Re: num_cmp Mystery

by Andrew Dabrowski -
That's what I meant by "evaluating numerically", rather than symbolically.

I don't see why it is, if the "correct" answer is 1.60305891143483e-14, that 0 is considered within the tolerance, but 3.06161699786838e-14 not.

Does anyone know exactly how num_cmp works?
In reply to Andrew Dabrowski

Re: num_cmp Mystery

by Andras Balogh -

I know I should not respond because I don't know how num_cmp works, but I just can't resist.

  • 100*cos(pi/2) and -100*cos(pi/2) are both marked correct.
  • 200*cos(pi/2) and -200*cos(pi/2) are both marked incorrect.
  • (500*pi/6)*cos(pi/2) is marked correct, it is approximately 260*cos(pi/2), which is marked incorrect along with the smaller 200*cos(pi/2).
It seems to me that it either takes the correct formula  (500*pi/6)*cos(pi/2) or a numerical approximation of 0.

In reply to Andrew Dabrowski

Re: num_cmp Mystery

by Alex Jordan -
If the answer checker was the checker from MathObjects for a Real (so, not num_cmp), then I believe 1e-14 is the "zero level". Anything smaller than that in absolute value is considered equivalent to 0. (Well, really if a value is smaller than that, then comparisons are made absolutely instead of relatively, with a tolerance of 1e-12. So that includes being equivalent to 0.)

Both of these numbers are more than 1e-14. The "zero level" would have to be something like 2e-14 for this to be the explanation, so it could be something else. And I see now Andras's post that suggest it is something else, not a zero level issue.

In reply to Alex Jordan

Re: num_cmp Mystery

by Danny Glin -
It's worth noting that num_cmp is not well-maintained. It is kept around for backwards-compatibility, but for newly authored problems you should be using the MathObjects checkers.
Since your answer is already a MathObject Real, you should be able to do
ANS($df->eval(t=> 3)->cmp());

I don't know if this will solve the issue you describe, but based on Alex's comments it should at least have predictable results when dealing with very small numbers.
In reply to Danny Glin

Re: num_cmp Mystery

by Glenn Rice -

Although you are correct about num_cmp not being maintained and using the cmp method is the correct way to do this now, the cmp method also has the same issue for this. If I am not mistaken num_cmp actually uses the MathObject methods in it's implementation.

I think Davide could shed some light on this situation.

In reply to Glenn Rice

Re: num_cmp Mystery

by Danny Glin -
You are correct that num_cmp uses the MathObject methods in the background, but I don't know how it handles being passed a MathObject directly.
Originally num_cmp expected either a perl number or a perl string as an argument. If you pass it a MathObject as in this example, I don't know if all of the attributes of the object and the context are passed to the checker.
That being said, this does not appear to be the root of the issue being discussed.