WeBWorK Main Forum

evaluating function outside domain

evaluating function outside domain

by Andrew Dabrowski -
Number of replies: 3
Here is a MWE. Two functions are defined and evaluated at pi/2. The first function is undefined at that point; where they are both defined they are equal.

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


$showPartialCorrectAnswers = 1;


$f = Formula("(tan(x)-3)/sec(x)");
$df = $f->D;
$dfval = $df->eval(x=>pi/2);

#$ans2 = $df->eval(x=>(pi/2));
$g = Compute("cos(x)+3*sin(x)");
$gval = $g->eval(x=>pi/2);


[`` df(x) = [$df] ``]

[`` df(\pi/2) = [$dfval] ``]

[`` g(x) = [$g] ``]
[`` g(\pi/2) = [$gval] ``]

What is df? [______________]{$df}


ENDDOCUMENT(); # This should be the last executable line in the problem.

This produces the output shown in the attached bmp file. Apparently WW tried to evaluate the first function (df) at pi/2, fails, and then tried to approximate the limit as x->pi/2, and doesn't quite succeed.

This has probably been discussed before. Is WW incapable of determining that df is undefined at pi/2? If it can be determined, wouldn't it be preferable to return some code for NaN rather than to take the limit?

Background: This came up in a problem one of our calc instructors found in the OPL. The question was to take the derivative of

f = (tan x - 3)/sec x.

The smart thing to do is simplify down to sin x - 3 cos x before differentiating. WW brute forced it with the quotient rule and got the mess of sec's and tan's seen in df. When a student entered 3 as the value of f'(pi/2) it was simply marked wrong.

Perhaps there is no good way to avoid this in general. But the error could have been diagnosed more easily if WW had made clear that its f' was undefined at pi/2, or at least thrown an error instead of simpler marking the answer of 3 as wrong.
Attachment ww_cos_3sin.png
In reply to Andrew Dabrowski

Re: evaluating function outside domain

by Davide Cervone -
Apparently WW tried to evaluate the first function (df) at pi/2, fails, and then tried to approximate the limit as x->pi/2, and doesn't quite succeed.

No, that is not what is happening. The evaluation didn't fail, and there is no limiting process involved at all. The issue is the standard one involved with numerical calculations via floating-point numbers: the finite precision of the numbers used.

What this means in this case is that pi (and therefore pi/2) are only approximations. Numbers are stored with a finite number of decimal places (about 16 to 17 digits), so you aren't evaluating at exactly π/2, but rather at a rational approximate of 16 to 17 digits precision. (Note that all numbers stored in floating-point notation on a computer are rational numbers.) So the function is defined for this value.

From a numerical standpoint, for a function that is undefined at an isolated irrational value, you will never be able to even try to evaluate the function at that value, since it can't even be represented in the set of numbers available to WeBWorK. (It is possible that the function can be undefined for other reasons, however, like the formula produces a number too large or too small to represent, but that isn't the case here.)

So since the function is actually defined at the value where we are evaluating it, why isn't the answer closer to 3? The reason here is again the finite precision being used. To understand what is going on, we need to look at the terms of the (unreduced) derivative more closely.

The derivative produced by the automatic differentiation is

            sec2(x)sec(x) - (tan(x) - 3)sec(x)tan(x)
The approximation to π/2 used by WeBWorK is 1.5707963267948966, and for this x, sec2(x)sec(x) is 4.3556947031103271 &times 1048, while (tan(x) - 2)sec(x)tan(x) is 4.3556947031103264 &times 1048. Note that these are equal except for the last two digits.

When you subtract these two values, the first 15 digits cancel each other, and you are left with just the 71 minus the 64, or just 7, with a lot of leading zeros. That is, of the 17 original digits, you are left with only one digit (and the least significant and most unreliable digit at that), and so there has been a catastrophic loss of precision. This is known as "subtractive cancelation", and it is the effect at the heart of most of the problems the occur in numerical computations via computer, not round-off error, as commonly thought. Round-off error is how you get bad digits down at the least significant digits, but it is subtractive cancelation that brings those bad digits into positions of significance.

The actual values internally are, of course, in binary, not decimal, and so the decimal versions I've given above are also not exactly the numbers used internally.

The difference between the two terms in the numerator is 6.4903710731685345 × 1032, and you can see that the change of magnitude from 1048 to 1032 represents a loss of 16 digits of precision. So at this point, the computation is essentially worthless, as we have no reliable data left (only the 6 is remotely useful, and even that is not very accurate -- the rest is complete garbage). So when it is divided by the value of sec2(x) from the denominator, we don't get very close to the expected value of 3. (To get that, the numerator would need to be closer to 8 × 1032). This is why you are getting 2.4335 instead of 3.

So this has nothing to do with WeBWorK failing to evaluate at π/2 and trying to take limits, and everything to do with how floating-point numbers are stored on a computer.

In terms of your comment asking for WeBWorK to inform you that the function is not defined for π/2, that is not really possible, since you are not actually using a value where it is undefined (and never can, since that number isn't representable as a floating-point real). WeBWorK, and its MathObjects library, is not a computer algebra system, it is a numeric evaluation system. These issues are inherent in that approach, and are things that problem authors do need to be aware of, particularly places where subtractive cancelation can occur.

My real question is why are you asking for f '(π/2) in the first place, since it doesn't exist for your f ? Since f itself is undefined at π/2 (neither the secant nor the tangent is defined at π/2), it is discontinuous there, and so the derivative is not defined there. The student's answer of 3 is incorrect.

In reply to Davide Cervone

Re: evaluating function outside domain

by tim Payer -

Wow, that was another great reply (from Davide Cervone) with specific detail.

I think it should be posted on the WeBWorK introduction to authoring problem's page.

I am encountering the same problem, and have made a fix with a function for rounding values.

My specific case is generating problems to give first year stat students practice at using Z-tables that use second decimal Z-scores which correspond to normal probability. Using the Sprint command I round all Z-scores down to the second decimal. This "most" of the time but not always. Those special cases where the Z-score conversion results in a terminating 5 at the third decimal will "fail" to round correctly about 30-40% of the time. For Example Z= 0.125, or Z = -2.175, etc... Printing the value will show that these numbers are represented not as 0.125 but as 0.124999999999989. So "rounding" this to the second decimal under the sprint command takes this value to Z = 0.12, not to the "correct" value of Z = 0.13. To make these Z-Table examples work I have used a function to call to check for this rounding conundrum.

The function follows this post:

A seemingly unrelated point that anticipates those who would retort that in the 21st century we as teachers should not be asking our students to use hard copy Z-tables in first year stat courses since the available software solves these problems so readily: No, you use Both and you teach Both. Why?

Because when you ask the student to find the closest probability to "0.19" within the body of Z-table and these students must choose (no interpolation here, just asking to find the nearest value in the Z-table) between 0.192150 and 0.189430, a good third of the class will choose incorrectly. Many will choose the first value because the first 2 digits match and why bother looking any further? These students need to make their response, be marked wrong, and then question their assumptions. They are all science majors and need to develop the discipline of detailed inspection. As do problem authors of WeBWorK.

#### The next 15 lines of code use a function (subroutine) to

#### Enforce a correct rounding for Z-score calculations that end at a "5"

#### on the third decimal: For example: 1.125, 2.755, -1.865, etc...

#### Because the Z-tables require a second decimal value rounding and

#### the binary perl calculation for a base ten value under the 2nd decimal Sprint

#### command ocaisionally "clips" the trailing 0.005, rather than rounding

#### the second decimal up. This function will enforce a correct 2nd rounding

#### for these third decimal values that end with a 5.

# Function definition

# Enable debugging

#$WeBWorK::Debug::Enabled = 1;

sub roundz{

# get total number of arguments passed.

$n = scalar(@_);

my $zscore = shift;

my $z2nd = shift;

$diff = abs($zscore-$z2nd);

$test = 0;

if((0.00499999 < $diff) && ($diff < 0.005000001)){

if(($zscore <= 0) && ($zscore < $z2nd)){

return $z2nd -0.01;


elsif(($zscore >= 0) && ($zscore > $z2nd)){

return $z2nd + 0.01;

} else {

$test = 70;


} else {

return $z2nd;



In reply to tim Payer

Re: evaluating function outside domain

by Michael Gage -

Wow, that was another great reply (from Davide Cervone) with specific detail.

I think it should be posted on the WeBWorK introduction to authoring problem's page.

Hi Tim,

You and others are welcome to help out with editing the author page. The wiki is editable by everyone. And the author page (and many others) - particularly the parts for new comers - indisputably need updating and reorganizing. It's a matter of bandwidth and finding enough people to help out (or even more important to help organize the effort. :-) )

Take care,