WeBWorK Main Forum

Understanding quotes and decimal approximations

Understanding quotes and decimal approximations

by Thomas Hoft -
Number of replies: 8

Hi all, 

I'm trying to understand quotes in defining variables using Compute() and how that impacts whether fractions are stored as fractions or six-digit decimal approximations. 

In the MWE below, typing fractions inside quotes seem to result in fractions staying as fractions, but everything else -- products of variables and substituting in to variables doesn't. I'm particularly interested in having $func_subbed keep the fraction, but it doesn't. How do I structure this so that the 42/9 stays 42/9 not 4.66667?

I think I saw a forum post that quotes will force conversion to strings, with attendant decimals, but perhaps I misunderstood that post, as it seems to contradict what I'm seeing here...

(This came up in a solution to an ODE with initial condition y(0)=0. When a random evaluation point in the answer checker was smaller than about 0.01 in magnitude, having six-decimal approximations for the fractional coefficients of four exponential and sin/cos terms resulted in the value of the webwork correct answer being different from the student's correct answer with fractions -- by greater than the tolerance. Of course setting $ans->limits=[0.5, 2] or increasing the tolerance works, but a more elegant and non-problem-specific solution would be nice.)

((The machine epsilon bit was just for fun...))

Thanks! - Thomas

DOCUMENT();      
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
$num_quote = Compute("42/9");
$num_noquo = Compute(42/9);

$func_sub = Compute("cos(3x)");
$func_typed = Compute("42/9*cos(3x)");
$func_quote = Compute("$num_quote $func_sub");
$func_noquo = Compute($num_noquo*$func_sub);
$func_perl = $num_quote*$func_sub;

Context()->variables->add(k=>"Real");
$const = Compute("31/2");
$func_const = Compute("k*sin(7x)");
$func_value = Compute("31/2*sin(7x)");
$func_subbed = $func_const->substitute(k=>$const);

$bonus1 = Compute("3(4/3-1)-1"); # just for fun...
$bonus2 = Compute(3*(4/3-1)-1); # as expected

BEGIN_PGML
1. Number with quotes: [_____]{$num_quote}
1. Number no quotes: [_____]{$num_noquo}
1. Function typed out: [____________]{$func_typed}
1. Function with quotes: [____________]{$func_quote}
1. Function no quotes: [____________]{$func_noquo}
1. Function via perl: [____________]{$func_perl}
1. Function with constant: [____________]{$func_const}
1. Function value typed in: [____________]{$func_value}
1. Function value subbed in: [____________]{$func_subbed}
1. 0: [_____]{$bonus1}
1. [`\epsilon`]: [_____]{$bonus2}
END_PGML
ENDDOCUMENT();        




Attachment wwans.png
In reply to Thomas Hoft

Re: Understanding quotes and decimal approximations

by Alex Jordan -

  • $num_quote = Compute("42/9");
    passed the string "42/9" to Compute(). Now $num_quote is a perl hash containing lots of information, including remembering that it was originally defined using the string "42/9". But it also knows that its numerical value is 4.66... (up to about 16 or 17 digits). Maybe something else will know to interpret that string as a mathematical fraction, but at this point $num_quote is not a fraction in any mathematical meaningful way. Now when $num_quote is used in some way where a string is needed, it can still provide "42/9" or it can provide "4.66..." (all digits). Or it will provide "4.66667" because the default context settings turn decimals into strings with only 6 digits of precision. It depends on how $num_quote is used. And take note that even if it provides "42/9", if you are passing that into something else (another Compute call, a Formula call, etc) then maybe that other thing will in turn cause 42/9 to be displayed as a decimal.
  • $num_noquo = Compute(42/9);
    had perl first do the division, getting 4.66... (up to about 16 or 17 significant digits). The string version of that is what Compute() receives. There is no hope of anything downstream remembering it was "42/9". (Although some tools might still try to turn that truncated decimal into a nearby fraction with a small denominator.)
Does that help to understand the behavior in the later expressions where you use $num_quote and $num_noquo? This does not address every discrepancy you are seeing, but if you can now narrow the question to one thing at a time, I could write more about individual discrepancies.

In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -
Alex,

Thanks for the reply.
That $num_noquo is stuck as floating-point makes perfect sense -- it started as floating-point. I would expect (hope? prefer?) that $func_quote, $func_perl, and also Compute($num_quote*$func_sub) would retain an understanding that I "meant" 42/9 not 4.66...
I would like the fraction to remain for display purposes; do you know of a way to do this?

You distinguish between display accuracy and computational accuracy. Here's where this originally cropped up:

DOCUMENT();      
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->variables->are(k1=>"Real", k2=>"Real", t=>"Real");
$y1 = Compute("e^(-4t)");
$y2 = Compute("e^(-2t)");
$yp = Compute("7/85 cos(t) + 6/85 sin(t)");
$y_gen = Compute("k1 $y1 + k2 $y2 + $yp");
$const_1 = Compute(2/17); # k1
$const_2 = Compute(-1/5); # k2
$y_spec = $y_gen->substitute(k1=>$const_1, k2=>$const_2); #decimal
#$y_spec = Compute("2/17 $y1 + -1/5 $y2 + $yp"); # decimal
#$y_spec = Compute("2/17 $y1 + -1/5 $y2 + 7/85 cos(t) + 6/85 sin(t)"); # fractions 
$y_spec->{test_points} = [ [1,1,0.005] ]; # ok at 0.00920 or greater
#$y_spec->{limits} = [0.5,5]; 
BEGIN_TEXT
(a) Find the general solution of the differential equation
\[ \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}. \]
\(\quad\) \(y(t) = \) \{ans_rule(50)\} 
$PAR \(\quad\) Use "k1" and "k2" for the constants in your solution.
$PAR (b) Find the solution of the initial-value problem 
\[ \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}, \quad y(0)=y'(0)=0. \]
\(\quad\) \(y(t) = \) \{ans_rule(50)\} 
END_TEXT
sub mycheck {
  my ($correct, $student, $ansHash) = @_;
  if ( $student->usesOneOf("k1") && # uses constant k1 -- not necessary?
       $student->usesOneOf("k2") && # uses constant k2 -- not necessary?
       ( ($student->substitute(k1=>1, k2=>0) == $y1 + $yp) || # one of terms is y1
         ($student->substitute(k1=>0, k2=>1) == $y1 + $yp) ) && 
       ( ($student->substitute(k1=>1, k2=>0) == $y2 + $yp) || # one of terms is y2
         ($student->substitute(k1=>0, k2=>1) == $y2 + $yp) ) && 
       $student->substitute(k1=>0, k2=>0) == $yp # probably redundant w/above...
     ) 
  { return 1; } else { return 0; } 
}
ANS($y_gen->cmp(checker=>~~&mycheck));
#ANS($y_spec->cmp);
ANS($y_spec->cmp(diagnostics=>1));
ENDDOCUMENT();        

I have three versions of $y_spec. The first is what I'd like because it minimizes copy/paste errors on my part -- I need only get $yp correct in one place. If one enters the correct function

2/17 e^(-4t) - 1/5 e^(-2t) + 7/85 cos(t) + 6/85 sin(t)

and an evaluation point is unluckily chosen in the interval [-0.0092,0.0092] then the answer is marked incorrect b/c the relative error is greater than 0.001 (I checked the interval). The answer display in this first version uses six-digit decimals. That got me thinking maybe the decimal approximation is the issue. So I put the decimal approximation version and the fraction version into Matlab and plotted the relative error -- it's larger than 0.001  on the interval [-0.0141,0.0141]. That's not identical to webwork/perl but not outlandishly different either. 

The second version of $y_spec exhibits the same behavior -- decimal display and marked incorrect. 

The third version of $y_spec displays fractions and marks the answer correct -- the relative error from ANS($y_spec->cmp(diagnostics=>1)) is on the order of 10^-12. 

So: is there something going on with keeping fewer digits than double-precision floating-point? Is this at all connected to the answer display?

Thanks in advance for your thoughts on this - Thomas

Attachment wwans2.png
In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -

Part of the diagnostics section for the screenshot above.

Attachment wwans2Diag.png
In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -

...and the Matlab plot showing large relative difference between a decimal approximation and the fraction version.

Attachment relDiff.png
In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -

Alex (or anyone else!), 

You point out that fractional or decimal depends on how it's used, and function calls may cause decimals instead of fractions -- is there a general rule for when it stays fractional and when it's converted to decimal? 

Here's a simplified version of the issue I'm having: 

How can I code something like

$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;

to generate a "Correct Answer" that typesets as

42/19 cos(sqrt(3)x) 

instead of 

4.66667*cos(1.73205*x)

Doing Compute("$num_quote $func_sub->{string}") does maintain the sqrt(3) but I can't figure out anything that maintains the 42/19. 

Same question for 

Context()->variables->add(k=>"Real");
$const = Compute("31/2");
$func_const = Compute("k*sin(sqrt7x)");
$func_subbed = $func_const->substitute(k=>$const);

Hopefully that illuminates the inner workings for me, and helps me get a handle on both answer display and the weird numerical tolerance issue I described above.

Thanks! - Thomas

In reply to Thomas Hoft

Re: Understanding quotes and decimal approximations

by Alex Jordan -
Sorry for not keeping up with your previous posts. I was traveling last week and forgot about this.

$num = Compute("42/9");

Paraphrasing from Davide Cervone about this: Compute() calls Function() on the string "42/9", after first setting the context flags reduceConstants, reduceConstantFunctions, and showExtraParens to 0. Then if the formula is constant (which it is in this case), the formula is evaluated to get a Real, which is already something with a floating point value. Then Formula() is once again called on the original string "42/9" (this time with the reductions turned off), and this is used to produce the string and TeX versions for the correct answer.

So $num is a Real. Its value method gives 4.666... Its string and TeX methods give 4.66667. And its correct_ans and correct_ans_latex_string properties (properties, not methods) are respectively '42/9' and '\frac{42}{9}'.

So the things you want (those fractions in the last two properties I mentioned) are just not what you get when you use $num as a string. Instead you get the result of the string method: 4.66667.

What you really want is either:

A. Explicitly make $num be a Formula() object, and use the context flag reduceConstants => 0. So:
Context()->flags->set(reduceConstants => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;

B. Use the Fraction context, which under default settings is also going to reduce your "42/9" to "14/3":
loadMacros("contextFraction.pl");
Context("Fraction");
$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;

However, both of these ways of doing it cause your "sqrt3" to be a decimal. This is because $func is the result of a Formula() times a Formula(), or a Fraction() times a Formula(). The result is a Formula() that did not get the special treatment that Compute() uses for setting those special string properties. So now what you probably want is to stop things like "sqrt(3)" from being executed. That's a function applied to a constant, and for that there is the flag with the slightly confusing name reduceConstantFunctions:

Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;

loadMacros("contextFraction.pl");
Context("Fraction")->flags->set(reduceConstantFunctions => 0);
$num = Compute("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -
Alex, 

No worries, and thank you

What you describe is the inner workings that I had (have?) no clue about. I'll think about whether I want to use Formula or the Fraction Context. Meantime this works. MWE below and screenshot attached, for anyone who encounters this later...

I'll revisit my weird numerical issue with this addition, and report back. 

Thanks again - Thomas

DOCUMENT();      
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$num = Formula("42/9");
$func_sub = Compute("cos(sqrt3x)");
$func = $num*$func_sub;
Context()->variables->add(k=>"Real");
$const = Formula("31/2");
$func_const = Compute("k*sin(sqrt7x)");
$func_subbed = $func_const->substitute(k=>$const);
BEGIN_PGML
1. Number: [____________]{$num}
1. Function: [____________]{$func}
1. Constant: [____________]{$func_const}
1. Subbed: [____________]{$func_subbed}
END_PGML
ENDDOCUMENT();        


Attachment wwansfix.png
In reply to Alex Jordan

Re: Understanding quotes and decimal approximations

by Thomas Hoft -

Alex, 

This also fixed the numerical issue that led me down this rabbit hole!

Thanks! - Thomas

DOCUMENT();     
loadMacros("PGstandard.pl", "PGML.pl", "PGcourse.pl");
Context("Numeric");
Context()->variables->are(k1=>"Real", k2=>"Real", t=>"Real");
Context()->flags->set(reduceConstants => 0, reduceConstantFunctions => 0);
$y1 = Compute("e^(-4t)");
$y2 = Compute("e^(-2t)");
$yp = Compute("7/85 cos(t) + 6/85 sin(t)");
$y_gen = Compute("k1 $y1 + k2 $y2 + $yp");
$const_1 = Formula("2/17"); # k1
$const_2 = Formula("-1/5"); # k2
$y_spec = $y_gen->substitute(k1=>$const_1, k2=>$const_2); 
$y_spec->{test_points} = [ [1,1,0.005] ];
$y_gen->cmp(diagnostics=>1, 
checker => sub {
  my ($correct, $student, $ansHash) = @_;
  if ( ( ($student->substitute(k1=>1, k2=>0) == $y1 + $yp) || # one of terms is y1
         ($student->substitute(k1=>0, k2=>1) == $y1 + $yp) ) && 
       ( ($student->substitute(k1=>1, k2=>0) == $y2 + $yp) || # one of terms is y2
         ($student->substitute(k1=>0, k2=>1) == $y2 + $yp) ) #&& 
     ) 
  { return 1; } else { return 0; } 
}
);
BEGIN_PGML
(a) Find the general solution of the differential equation
[`\displaystyle \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}. `]
[`y(t) = `] [______________________________________________________]{$y_gen}
Use "k1" and "k2" for the constants in your solution.
(b) Find the solution of the initial-value problem 
[`\displaystyle \frac{d^2y}{dt^2} + 6 \frac{dy}{dt} + 8 y = \cos{t}, \quad y(0)=y'(0)=0. `]
[`y(t) = `] [_______________________________________________________]{$y_spec}
END_PGML ENDDOCUMENT();        


Attachment wwDE.png