[system] / trunk / pg / macros / contextScientificNotation.pl Repository:
ViewVC logotype

View of /trunk/pg/macros/contextScientificNotation.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5346 - (download) (as text) (annotate)
Fri Aug 17 18:30:34 2007 UTC (12 years, 3 months ago) by dpvc
File size: 9971 byte(s)
This file implements a context where students can enter numbers in
scientific notation.  You can control the number of decimal digits
they need/can enter, and how many are used when the answers are
displayed.

See the comments at the top of the file for more details.

    1 
    2 =pod
    3 
    4  #########################################################################
    5  #
    6  #  This file implements a context in which students can enter
    7  #  answers in scientific notation.  It tries hard to report
    8  #  useful error messages when the student's answer is not
    9  #  in the proper format, and it also allows you to control
   10  #  how many decimal digits they are allowed/required to
   11  #  enter, and how many the system will display.
   12  #
   13  #  This probably should be called LimitedScientificNotation
   14  #  since it does not allow any operations other than the ones
   15  #  needed in Scientific notation.  In the future it may be
   16  #  renamed if we produce a computational scientific notation
   17  #  context.
   18  #
   19  #  To use this context, add
   20  #
   21  #    loadMacros("contextScientificNotation.pl");
   22  #
   23  #  to the top of your problem file, and then use
   24  #
   25  #    Context("ScientificNotation");
   26  #
   27  #  to select the contxt and make it active.  You can create
   28  #  values in scientific notation in two ways:
   29  #
   30  #    $n1 = Compute("1.23 x 10^3");
   31  #
   32  #  or
   33  #
   34  #    $n2 = ScientificNotation(1.23 * 10**3);
   35  #
   36  #  (or even $n2 = ScientificNotation(1230), and it will be converted).
   37  #
   38  #  You can control how many digits are displayed by setting the
   39  #  snDigits flag in the context.  For example,
   40  #
   41  #    Context()->flags->set(snDigits=>2);
   42  #
   43  #  sets the context to display at most 2 digits.  The default is 6.
   44  #  By default, trailing zeros are removed, but you can ask that
   45  #  they be retained by issuing the command
   46  #
   47  #    Context()->flags->set(snTrimZeros=>0);
   48  #
   49  #  It is also possible to specify how many decimal digits the
   50  #  student must enter.  For example,
   51  #
   52  #    Context()->flags->set(snMinDigits=>3);
   53  #
   54  #  would require the student to enter at least 3 digits past
   55  #  the decimal place (for a total of 4 significant digits,
   56  #  including the one to the left of the decimal).  The default
   57  #  is 1 digit beyond the decimal.  A value of 0 means that
   58  #  a decimal point and decimal values is optional.
   59  #
   60  #  Similarly,
   61  #
   62  #    Context()->flags->set(snMaxDigits=>6);
   63  #
   64  #  sets the maximum number to 6, so the student can't enter
   65  #  more than that.  Setting this to 0 means no decimal places
   66  #  are allowed, effectively meaning students can only enter
   67  #  the numbers 0 through 9 (times a power of 10).  Setting
   68  #  this to a negative number means that there is no upper
   69  #  limit on the number of digits the student may enter (this
   70  #  is the default).
   71  #
   72  #  As an example, in order to force a fixed precision of
   73  #  three digits of precision, use
   74  #
   75  #    Context()->flags->set(
   76  #      snDigits => 3,
   77  #      snTrimZeros => 0,
   78  #      snMinDigits => 3,
   79  #      snMaxDigits => 3,
   80  #    );
   81  #
   82  #  Note that if you restrict the number of digits, you may
   83  #  need to adjust the tolerance values since the student
   84  #  may not be allowed to enter a more precise answer.  In
   85  #  the example above, it would be appropriate to set the
   86  #  tolerance to .0001 and the tolType to "relative" in
   87  #  order to require the answers to be correct to the three
   88  #  digits that are shown.
   89  #
   90  #
   91 
   92 =cut
   93 
   94 sub _contextScientificNotation_init {ScientificNotation::Init()}
   95 
   96 ######################################################################
   97 
   98 package ScientificNotation;
   99 
  100 #
  101 #  Creates and initializes the ScientificNotation context
  102 #
  103 sub Init {
  104   #
  105   #  Create the Scientific Notation context
  106   #
  107   my $context = $main::context{ScientificNotation} = Parser::Context->getCopy(undef,"Numeric");
  108 
  109   #
  110   #  Make numbers include the leading + or - and not allow E notation
  111   #
  112   $context->{pattern}{number} = '[-+]?(?:\d+(?:\.\d*)?|\.\d+)';
  113 
  114   #
  115   #  Remove all the stuff we don't need
  116   #
  117   $context->variables->clear;
  118   $context->constants->clear;
  119   $context->parens->remove('(','[','{','|');
  120   $context->operators->clear;
  121   $context->functions->clear;
  122   $context->strings->clear;
  123 
  124   #
  125   #  Only allow  x  and  ^  operators
  126   #
  127   $context->operators->add(
  128      'x' => {precedence => 3, associativity => 'left', type => 'bin',
  129              string => 'x', TeX => '\times ', perl => '*',
  130              class => 'ScientificNotation::BOP::x'},
  131 
  132      '^' => {precedence => 7, associativity => 'right', type => 'bin',
  133              string => '^', perl => '**',
  134              class => 'ScientificNotation::BOP::power'},
  135 
  136      '**'=> {precedence => 7, associativity => 'right', type => 'bin',
  137              string => '^', perl => '**',
  138              class => 'ScientificNotation::BOP::power'}
  139   );
  140 
  141   #
  142   #  Don't reduce constant values (so 10^2 won't be replaced by 100)
  143   #
  144   $context->flags->set(reduceConstants => 0);
  145   #
  146   #  Flags controlling input and output
  147   #
  148   $context->flags->set(
  149     snDigits => 6,     # number of decimal digits in mantissa for output
  150     snTrimZeros => 1,  # 1 means remove trailing 0's, 0 means leave them
  151     snMinDigits => 1,  # minimum number of decimal digits to require in student input
  152                        #  (0 means no decimal is required)
  153     snMaxDigits => -1, # maximum number of decimals allowed in student input
  154                        #  (negative means no limit)
  155   );
  156 
  157   #
  158   #  Better error message for this case
  159   #
  160   $context->{error}{msg}{"Unexpected character '%s'"} = "'%s' is not allowed in scientific notation";
  161 
  162   #
  163   #  Hook into the Value package lookup mechanism
  164   #
  165   $context->{value}{ScientificNotation} = 'ScientificNotation::Real';
  166 
  167   #
  168   #  Create the constructor function
  169   #
  170   main::PG_restricted_eval('sub ScientificNotation {Value->Package("ScientificNotation")->new(@_)}');
  171 }
  172 
  173 
  174 ##################################################
  175 #
  176 #  The Scientific Notation multiplication operator
  177 #
  178 package ScientificNotation::BOP::x;
  179 our @ISA = qw(Parser::BOP);
  180 
  181 #
  182 #  Check that the operand types are compatible, and give
  183 #  approrpiate error messages if not.  (We have to work
  184 #  hard to make a good message about the number of
  185 #  decimal digits required.)
  186 #
  187 sub _check {
  188   my $self = shift;
  189   my ($lop,$rop) = ($self->{lop},$self->{rop});
  190   my ($m,$M) = ($self->context->flag("snMinDigits"),$self->context->flag("snMaxDigits"));
  191   $M = $m if $M >= 0 && $M < $m;
  192   my $repeat = ($M < 0 ? "{$m,}" : "{$m,$M}");
  193   my ($digits,$zeros) = ("\\.\\d$repeat","\\.0$repeat");
  194   my $zero = ($m == 0 ? ($M > 0 ? "0.0" : "0") : "0.".("0"x$m));
  195   my $decimals = ($m == $M ? ($m == 0 ? "no digits" : "exactly $m digit".($m == 1 ? "" : "s")) :
  196                  ($M < 0 ?   ($m == 0 ? "" : "at least $m digit".($m == 1 ? "" : "s")) :
  197                              ($m == 0 ? "at most $M digit".($M == 1 ? "" : "s") :
  198                                         "between $m and $M digits")));
  199   $decimals = " and ".$decimals." after it" if $decimals;
  200   $digits = "($digits)?", $zeros = "($zeros)?" if $m == 0;
  201   $self->Error("You must use a power of 10 to the right of 'x' in scientific notation") unless $rop->{isPowerOf10};
  202   $self->Error("You must use a number to the left of 'x' in scientific notation") unless $lop->type eq 'Number';
  203   $self->Error("The number to the left of 'x' must be %s, or have a single, non-zero digit before the decimal%s",
  204                $zero,$decimals) unless $lop->{value_string} =~ m/^[-+]?([1-9]${digits}|0${zeros})$/;
  205   $self->{type} = $Value::type{Number};
  206   $self->{isScientificNotation} = 1;  # mark it so we can tell later on
  207 }
  208 
  209 #
  210 #  Perform the multiplication and return a ScientificNotation object
  211 #
  212 sub _eval {
  213   my ($self,$a,$b) = @_;
  214   Value->Package("ScientificNotation")->make($self->context,$a*$b);
  215 }
  216 
  217 #
  218 #  Use the ScientificNotation MathObject to produce the output formats
  219 #  (if other operators are added back into the context, these will
  220 #   need to be modified to include parens at the appropriate times)
  221 #
  222 sub string {(shift)->eval->string}
  223 sub TeX    {(shift)->eval->TeX}
  224 sub perl   {(shift)->eval->perl}
  225 
  226 ##################################################
  227 #
  228 #  Scientific Notation exponentiation operator
  229 #
  230 package ScientificNotation::BOP::power;
  231 our @ISA = qw(Parser::BOP::power);  # inherit from standard power (TeX method in particular)
  232 
  233 #
  234 #  Check that the operand types are compatible and
  235 #  produce appropriate errors if not
  236 #
  237 sub _check {
  238   my $self = shift;
  239   my ($lop,$rop) = ($self->{lop},$self->{rop});
  240   $self->Error("The base can not have decimal places in scientific notation")
  241     if $lop->{value} == 10 && $lop->{value_string} =~ m/\./;
  242   $self->Error("You must use a power of 10 in scientific notation")
  243     unless $lop->{value_string} eq "10";
  244   $self->Error("The expondent must be an integer in scientific notation")
  245     unless $rop->{value_string} =~ m/^[-+]?\d+$/;
  246   $self->{type} = $Value::type{Number};
  247   $self->{isPowerOf10} = 1;  # mark it so BOP::x above can recognize it
  248 }
  249 
  250 #####################################
  251 #
  252 #  A subclass of Real that handles scientific notation
  253 #
  254 package ScientificNotation::Real;
  255 our @ISA = ("Value::Real");
  256 
  257 #
  258 #  Override these so we can mark ourselves as scientific notation
  259 #
  260 sub new {
  261   my $self = (shift)->SUPER::new(@_);
  262   $self->{isValue} = $self->{isScientificNotation} = 1;
  263   return $self;
  264 }
  265 
  266 sub make {
  267   my $self = (shift)->SUPER::make(@_);
  268   $self->{isValue} = $self->{isScientificNotation} = 1;
  269   return $self;
  270 }
  271 
  272 #
  273 #  Stringify using x notation not E,
  274 #  using the right number of digits, and trimming
  275 #  if requested.
  276 #
  277 sub string {
  278   my $self = shift;
  279   my $digits = $self->getFlag("snDigits");
  280   my $trim = ($self->getFlag("snTrimZeros") ? '0*' : '');
  281   my $r = main::spf($self->value,"%.${digits}e");
  282   $r =~ s/(\d)${trim}e\+?(-?)0*(\d)/$1 x 10^$2$3/i;
  283   return $r;
  284 }
  285 
  286 #
  287 #  Convert x notation to TeX form
  288 #
  289 sub TeX {
  290   my $r = (shift)->string;
  291   $r =~ s/x/\\times /;
  292   $r =~ s/\^(.*)/^{$1}/;
  293   return $r;
  294 }
  295 
  296 #
  297 #  What to call us in error messages
  298 #
  299 sub cmp_class {"Scientific Notation"}
  300 
  301 #
  302 #  Only match against strings and Scientific Notation
  303 #
  304 sub typeMatch {
  305   my $self = shift; my $other = shift; my $ans = shift;
  306   return $other->{isScientificNotation};
  307 }
  308 
  309 #########################################################################
  310 
  311 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9