WeBWorK Main Forum

Enhance precision for Real types

Enhance precision for Real types

by Philip Loewen -
Number of replies: 7

I am surprised by what these commands do in the preamble of a problem file:

Context("Numeric");
$pi = 4*atan(1);
$s1 = sprintf("%18.15f",$pi);
$s2 = sprintf("%18.15f", pi);
$s3 = sprintf("%18.15f",$pi-pi);

Displaying the values of the string variables defined here produces this:

$s1 = 3.141592653589793
$s2 = 3.141590000000000
$s3 = 0.000000000000000

The first one shows that Perl itself handles floating-point calculations as expected; the second shows that the built-in variable "pi" provided by WeBWorK has much lower precision than Perl's native calculations; and the third calls for a re-interpretation of the subtraction symbol that I find quite uncomfortable.

Two questions arise.

  1. Why does "$pi-pi" evaluate to 0 here?
  2. Can I do something at the level of an individual .pg file to compel WeBWorK to keep more precision in its floating-point types and associated objects (like "Formula"s)?

I address WeBWorK through an institutionally-provided installation of Version 2.15, so remedies that do not require intervention at the server level (where I don't have access) would be especially welcome. Thanks!

In reply to Philip Loewen

Re: Enhance precision for Real types

by Glenn Rice -

You are not quite making the correct conclusion.  The PG pi function returns a math object.  When you print that it is converted to a string that displays with the accuracy that the current context for the number format display which by default is 5 decimal places.  The actual number has the same accuracy as the perl 4*tan(1), and that is why when you subtract the two you get 0. 

Add the line

Context()->{format}{number} = "%18.15f";

right after the line containing Context("Numeric") and before the other lines, and see what happens.

Without setting the context number format you can also use the value of the math object to see more precision.  So instead do

$s2 = sprintf("%18.15f", pi->value);

In reply to Glenn Rice

Re: Enhance precision for Real types

by Philip Loewen -
Thanks a lot. This is informative and thought-provoking. Let me adjust my question, as follows.

Evidently a math object number carries around a high-precision value, but only reveals/uses it under suitable conditions. (E.g., this happens automatically in some numerical calculations, but only by-request when passed to the function sprintf.) So: is there a simple way to be sure that the full accuracy of a math object number gets used all steps of a problem? Or, are there dangerous situations to avoid in which unexpected loss of accuracy can occur?

(These questions all started when I asked WW to display an expression whose value ought to have been 0, but whose printed form was an algebraic monstrosity that eventually reduced to something like 0.00004*exp(-x). Even inside WW, the expression seemed to be returning rather terrible approximations for 0 ... in the range of 1E-5, not in the range of machine-epsilon. I would like to understand what happened, and avoid similar problems in the future.)
In reply to Philip Loewen

Re: Enhance precision for Real types

by Alex Jordan -

If $pi is the MathObject Real(pi), then if you ask for it to be represented as a string, it will be represented to 6 significant digits (assuming you have left the defaults in place). Among other reasons, this prevents students from seeing floating point rounding error. So when rounding error has produced 0.9999999999936103 when it should have produced 1, the student will still see 1 on the screen.

This applies to any time $pi is needed in a string context. I guess that includes passing $pi to sprintf.

But if you are doing math with $pi, the fully stored value is used. So in your example, $pi-pi is 0, and that is passed to sprintf. So the "suitable conditions" are when doing arithmetic, function evaluation, etc.

> is there a simple way to be sure that the full accuracy of a math object number gets used all steps of a problem?

The full stored value is used at all steps if none of the steps ask for it to be used in a string context. For example Real("$pi/2") is just 1.5708 (no more digits), but Real($pi/2) is 1.57079633.... (up to about 16 places).


> (These questions all started when I asked WW to display an expression whose value ought to have been 0, but whose printed form was an algebraic monstrosity that eventually reduced to something like 0.00004*exp(-x). Even inside WW, the expression seemed to be returning rather terrible approximations for 0 ... in the range of 1E-5, not in the range of machine-epsilon. I would like to understand what happened, and avoid similar problems in the future.)


I'd be happy to comment more if you can provide the actual example/scenario in full.

In reply to Alex Jordan

Re: Enhance precision for Real types

by Philip Loewen -
Thanks to both colleagues above, I achieved what I wanted by wrapping some key lines where strings seemed inevitable with these instructions:

Context()->{format}{number} = "%18.15f";
... Context()->{format}{number} = "%g";
For general interest, here's how I got into all this.

My problem asked for a particular solution to a differential equation of the form ay" + by' + cy = f(x), where the constants a,b,c were all parametrized, and so was the function f(x)=M*x*exp(kx). Mathematically the simplest way to check the answer would be to work out the discrepancy between L[y]=ay"+by'+cy and f using whatever the student entered for y. I wrote a custom answer-checker to do just this, involving (among other things)

my ($correct,$y,$ansHash) = @_;  # get correct and student MathObjects
my $LHS = $a*($y->D('x','x')) + $b*($y->D('x')) + $c*$y; my $mismatch = Formula("$LHS - $f")

Somewhere in here (certainly in the third line, maybe sooner?) there are string conversions, so this is the block I wrapped with the format upgrade commands mentioned earlier.
In reply to Philip Loewen

Re: Enhance precision for Real types

by Davide Cervone -
Why not just use
    my $mismatch = $LHS - $f;
directly? No need to convert to strings and then re-parse.
In reply to Davide Cervone

Re: Enhance precision for Real types

by Philip Loewen -
The literal answer to "Why not just ...?" is that my understanding of MathObjects and how they work is so fragmentary that I never imagined even trying this. Thanks for filling in another gap in my knowledge.

To make my answer-checker, I started by copying this line from somewhere on the Wiki:
my ($correct,$y,$ansHash) = @_;  # get correct and student MathObjects

But I must admit I still don't know what type of MathObjects this step makes available under the names $correct, $y, and $ansHash. (And some Wiki pages I have read suggest that the answer might be, "It depends.") Is there something simple that I could read to learn all this, or is there some way to get WeBWorK to reveal what type it ascribes to a given object? I'd prefer the latter because it feels like "active learning" ... but a constraint I face is that my WW comes from a monolithic institution-level server, so all my experiments have to be expressible in the form of a *.pg problem file.

Thanks again.