WeBWorK Problems

calculations within a do-until loop

calculations within a do-until loop

by Bruce Yoshiwara -
Number of replies: 2
I’m trying to code a compound interest problem A=P(1+r/n)^(nt) asking students to find the least value of n, the number of times interest is compounded, to attain a specific value A (when the initial principal P, interest rate r, and number of years t are fixed).

I defined a function that seems to evaluate correctly when asked for values of A for n = 1, 2, 12, 52, and 365.

I then tried to use a do-until loop for WeBWorK to determine a desired value of n. My code seemed to work ok to find n for a value of A attained at n=16. But I got an incorrect value of n=33 when the desired value of A should have required n=30; I got an incorrect value of n=69 when the correct value was n=59, and WeBWorK timed out in another case when n should have been 553.

Here’s a partial version I’ve been trying to debug.

DOCUMENT();

loadMacros(
"PGstandard.pl",
"MathObjects.pl",
"PGML.pl",
"PGchoicemacros.pl",
"niceTables.pl",
);

Context()->flags->set( reduceConstants => 0, reduceConstantFunctions => 0 );
Context()->variables->add( n => "Real" );
Context()->flags->set(limits=>[1,365]);

$p0 = 1000;
$t = 5;
$r0 = 12;
$r = $r0/100;
$f = Formula("$p0*(1 + $r/n)^($t*n)");

#######################################################
### this is just trying to find my error in defining f
$a = $f->eval(n=>(15));
$b = $f->eval(n=>(16));
$one = $f->eval(n=>(30));
$two = $f->eval(n=>(31));
$three = $f->eval(n=>(58));
$four = $f->eval(n=>(59));
$five = $f->eval(n=>(552));
$six = $f→eval(n=>(553));
### or maybe limitations in WeBWorK’s evaluating f
################################################


# here is a loop trying to find a value of n
$p1 = 1818;
$n1 = 12;
do{
$temp1 = $f->eval(n=>($n1+2));
$n1 = $n1+1;
} until ( $temp1 > $p1 );

# here is a loop trying to find a value of n
$p2 = 1820;
$n2 = $n1;
do{
$temp2 = $f->eval(n=>($n2+2));
$n2 = $n2+1;
} until ($temp2 >$p2);

# here is a loop trying to find a value of n
$p3 = 1821; #want 1822 but get timeout
$n3 = $n2;
do{
$temp3 = $f->eval(n=>($n3+2));
$n3 = $n3+1;
} until ($temp3 > $p3);

Context()->{format}{number} = "%.6f#";

BEGIN_PGML

[`n=15`] gives [$a], [`n=16`] gives [$b]

[`n=30`] gives [$one], [`n=31`] gives [$two], [`n=58`] gives [$three], [`n=59`] gives [$four],

[`n=552`] gives [$five], [`n=553`] gives [$six],


Use the formula for compound interest,
>>[``A=P\left(1+\dfrac{r}{n} \right)^{nt} ``]<<

Suppose you invest $[$p0] at [$r0]% annual interest for [$t] years. In this problem, we will investigate how the number of compounding periods, [`n`], affects the amount, [`A`].
c.
What value of [` n `] is necessary to produce an amount [` A\gt [$p1]`]? [`n=`][___] To produce [` A\gt [$p2] `]? [`n=`][___] To produce [` A\gt [$p3] `]? [`n=`][___]

END_PGML

ANS(Compute("$n1")->cmp( tolType => 'absolute', tolerance => .5,) );
ANS(Compute("$n2")->cmp( tolType => 'absolute', tolerance => .5,) );
ANS(Compute("$n3")->cmp( tolType => 'absolute', tolerance => .5,) );

ENDDOCUMENT(); # This should be the last executable line in the problem.
In reply to Bruce Yoshiwara

Re: calculations within a do-until loop

by Davide Cervone -
The reason your loop runs longer than you expect is because the computations that you perform with MathObjects produce MathObject results. That means that your values of $temp1, $temp2, and $temp3, for example, are all MathObject Reals, not Perl reals. That means that when you do comparisons with them, they are fuzzy comparisons. That is, they respect the tolerance and tolType values in the context. Note that this applies not just to equality checks, but also to inequality checks as well. This is because you don't want both $a == $b and $a > $b to be true at the same time. That is, $a < $b, $a == $b, and $a > $b should be mutually exclusive. The MathObject comparisons are arranged to work that way.

In your case, if you get a value like 1822.0004823, this is considered to be equal to 1822 by the fuzzy comparison, so $tmp3 > $p3 is false even though $p3 = 1822. Indeed, the limit as n goes to infinity of your formula is $p0*e^(t*r), which is 1822.1188, which is fuzzy-equal to 1822, so you never get $temp3 > $p3, and the is why the problem times out.

You probably want to convert to perl reals rather than MathObjects for the computation. You can use $temp3->value for that, as this is the internal Perl real that underlies the MathObject Real. Also, if you are going to be evaluating a MathObject formula multiple times, you may want to create a perl function from the formula rather than calling eval() repeatedly, as that will be much more efficient. For example, $F = $f->perlFunction will get you a perl subroutine reference as $F and you can call $temp3 = &$F($n3)->value to get the Perl real result of the function at $n3.

Even so, however, your loop is pretty inefficient. You could use a faster algorithm, for example the bisection algorithm (or Newton's method if you want to get fancy about it). Here is one approach:

$p0 = 1000;
$t = 5;
$r0 = 12;
$r = $r0/100;
$f = Formula("$p0*(1 + $r/n)^($t*n)");
$F = $f->perlFunction;

$p1 = 1822;
#
#  Assume the value you want is between these two
#  (they should be a power of two apart).
#
$n0 = 12; $n1 = $n0 + 2**10;

#
#  Use bisection to locate the two values that are
#  one apart on opposite sides of the desired value.
#  (In this case, it will take 10 iterations to find it
#  as opposed to the 541 iterations for your version
#  that simply increments $n3, which is 54 times faster.)
#
while ($n1 - $n0 > 1) {
  $n2 = ($n0 + $n1) / 2;
  $f2 = &$F($n2)->value;
  if ($f2 > $p1) {$n1 = $n2} else {$n0 = $n2}
}
#
#  $n0 is below the value, $n1 is above it.
#
TEXT($n1);
Hope that clears up the issues for you.