[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 5370 - (download) (as text) (annotate)
Sun Aug 19 00:48:19 2007 UTC (12 years, 6 months ago) by dpvc
File size: 9996 byte(s)
Updated calls to Parser::Context->getCopy to remove unneeded undef
argument.

    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 loadMacros("MathObjects.pl");
   95 
   96 sub _contextScientificNotation_init {ScientificNotation::Init()}
   97 
   98 ######################################################################
   99 
  100 package ScientificNotation;
  101 
  102 #
  103 #  Creates and initializes the ScientificNotation context
  104 #
  105 sub Init {
  106   #
  107   #  Create the Scientific Notation context
  108   #
  109   my $context = $main::context{ScientificNotation} = Parser::Context->getCopy("Numeric");
  110 
  111   #
  112   #  Make numbers include the leading + or - and not allow E notation
  113   #
  114   $context->{pattern}{number} = '[-+]?(?:\d+(?:\.\d*)?|\.\d+)';
  115 
  116   #
  117   #  Remove all the stuff we don't need
  118   #
  119   $context->variables->clear;
  120   $context->constants->clear;
  121   $context->parens->remove('(','[','{','|');
  122   $context->operators->clear;
  123   $context->functions->clear;
  124   $context->strings->clear;
  125 
  126   #
  127   #  Only allow  x  and  ^  operators
  128   #
  129   $context->operators->add(
  130      'x' => {precedence => 3, associativity => 'left', type => 'bin',
  131              string => 'x', TeX => '\times ', perl => '*',
  132              class => 'ScientificNotation::BOP::x'},
  133 
  134      '^' => {precedence => 7, associativity => 'right', type => 'bin',
  135              string => '^', perl => '**',
  136              class => 'ScientificNotation::BOP::power'},
  137 
  138      '**'=> {precedence => 7, associativity => 'right', type => 'bin',
  139              string => '^', perl => '**',
  140              class => 'ScientificNotation::BOP::power'}
  141   );
  142 
  143   #
  144   #  Don't reduce constant values (so 10^2 won't be replaced by 100)
  145   #
  146   $context->flags->set(reduceConstants => 0);
  147   #
  148   #  Flags controlling input and output
  149   #
  150   $context->flags->set(
  151     snDigits => 6,     # number of decimal digits in mantissa for output
  152     snTrimZeros => 1,  # 1 means remove trailing 0's, 0 means leave them
  153     snMinDigits => 1,  # minimum number of decimal digits to require in student input
  154                        #  (0 means no decimal is required)
  155     snMaxDigits => -1, # maximum number of decimals allowed in student input
  156                        #  (negative means no limit)
  157   );
  158 
  159   #
  160   #  Better error message for this case
  161   #
  162   $context->{error}{msg}{"Unexpected character '%s'"} = "'%s' is not allowed in scientific notation";
  163 
  164   #
  165   #  Hook into the Value package lookup mechanism
  166   #
  167   $context->{value}{ScientificNotation} = 'ScientificNotation::Real';
  168 
  169   #
  170   #  Create the constructor function
  171   #
  172   main::PG_restricted_eval('sub ScientificNotation {Value->Package("ScientificNotation")->new(@_)}');
  173 }
  174 
  175 
  176 ##################################################
  177 #
  178 #  The Scientific Notation multiplication operator
  179 #
  180 package ScientificNotation::BOP::x;
  181 our @ISA = qw(Parser::BOP);
  182 
  183 #
  184 #  Check that the operand types are compatible, and give
  185 #  approrpiate error messages if not.  (We have to work
  186 #  hard to make a good message about the number of
  187 #  decimal digits required.)
  188 #
  189 sub _check {
  190   my $self = shift;
  191   my ($lop,$rop) = ($self->{lop},$self->{rop});
  192   my ($m,$M) = ($self->context->flag("snMinDigits"),$self->context->flag("snMaxDigits"));
  193   $M = $m if $M >= 0 && $M < $m;
  194   my $repeat = ($M < 0 ? "{$m,}" : "{$m,$M}");
  195   my ($digits,$zeros) = ("\\.\\d$repeat","\\.0$repeat");
  196   my $zero = ($m == 0 ? ($M > 0 ? "0.0" : "0") : "0.".("0"x$m));
  197   my $decimals = ($m == $M ? ($m == 0 ? "no digits" : "exactly $m digit".($m == 1 ? "" : "s")) :
  198                  ($M < 0 ?   ($m == 0 ? "" : "at least $m digit".($m == 1 ? "" : "s")) :
  199                              ($m == 0 ? "at most $M digit".($M == 1 ? "" : "s") :
  200                                         "between $m and $M digits")));
  201   $decimals = " and ".$decimals." after it" if $decimals;
  202   $digits = "($digits)?", $zeros = "($zeros)?" if $m == 0;
  203   $self->Error("You must use a power of 10 to the right of 'x' in scientific notation") unless $rop->{isPowerOf10};
  204   $self->Error("You must use a number to the left of 'x' in scientific notation") unless $lop->type eq 'Number';
  205   $self->Error("The number to the left of 'x' must be %s, or have a single, non-zero digit before the decimal%s",
  206                $zero,$decimals) unless $lop->{value_string} =~ m/^[-+]?([1-9]${digits}|0${zeros})$/;
  207   $self->{type} = $Value::type{Number};
  208   $self->{isScientificNotation} = 1;  # mark it so we can tell later on
  209 }
  210 
  211 #
  212 #  Perform the multiplication and return a ScientificNotation object
  213 #
  214 sub _eval {
  215   my ($self,$a,$b) = @_;
  216   $self->Package("ScientificNotation")->make($self->context,$a*$b);
  217 }
  218 
  219 #
  220 #  Use the ScientificNotation MathObject to produce the output formats
  221 #  (if other operators are added back into the context, these will
  222 #   need to be modified to include parens at the appropriate times)
  223 #
  224 sub string {(shift)->eval->string}
  225 sub TeX    {(shift)->eval->TeX}
  226 sub perl   {(shift)->eval->perl}
  227 
  228 ##################################################
  229 #
  230 #  Scientific Notation exponentiation operator
  231 #
  232 package ScientificNotation::BOP::power;
  233 our @ISA = qw(Parser::BOP::power);  # inherit from standard power (TeX method in particular)
  234 
  235 #
  236 #  Check that the operand types are compatible and
  237 #  produce appropriate errors if not
  238 #
  239 sub _check {
  240   my $self = shift;
  241   my ($lop,$rop) = ($self->{lop},$self->{rop});
  242   $self->Error("The base can not have decimal places in scientific notation")
  243     if $lop->{value} == 10 && $lop->{value_string} =~ m/\./;
  244   $self->Error("You must use a power of 10 in scientific notation")
  245     unless $lop->{value_string} eq "10";
  246   $self->Error("The expondent must be an integer in scientific notation")
  247     unless $rop->{value_string} =~ m/^[-+]?\d+$/;
  248   $self->{type} = $Value::type{Number};
  249   $self->{isPowerOf10} = 1;  # mark it so BOP::x above can recognize it
  250 }
  251 
  252 #####################################
  253 #
  254 #  A subclass of Real that handles scientific notation
  255 #
  256 package ScientificNotation::Real;
  257 our @ISA = ("Value::Real");
  258 
  259 #
  260 #  Override these so we can mark ourselves as scientific notation
  261 #
  262 sub new {
  263   my $self = (shift)->SUPER::new(@_);
  264   $self->{isValue} = $self->{isScientificNotation} = 1;
  265   return $self;
  266 }
  267 
  268 sub make {
  269   my $self = (shift)->SUPER::make(@_);
  270   $self->{isValue} = $self->{isScientificNotation} = 1;
  271   return $self;
  272 }
  273 
  274 #
  275 #  Stringify using x notation not E,
  276 #  using the right number of digits, and trimming
  277 #  if requested.
  278 #
  279 sub string {
  280   my $self = shift;
  281   my $digits = $self->getFlag("snDigits");
  282   my $trim = ($self->getFlag("snTrimZeros") ? '0*' : '');
  283   my $r = main::spf($self->value,"%.${digits}e");
  284   $r =~ s/(\d)${trim}e\+?(-?)0*(\d)/$1 x 10^$2$3/i;
  285   return $r;
  286 }
  287 
  288 #
  289 #  Convert x notation to TeX form
  290 #
  291 sub TeX {
  292   my $r = (shift)->string;
  293   $r =~ s/x/\\times /;
  294   $r =~ s/\^(.*)/^{$1}/;
  295   return $r;
  296 }
  297 
  298 #
  299 #  What to call us in error messages
  300 #
  301 sub cmp_class {"Scientific Notation"}
  302 
  303 #
  304 #  Only match against strings and Scientific Notation
  305 #
  306 sub typeMatch {
  307   my $self = shift; my $other = shift; my $ans = shift;
  308   return $other->{isScientificNotation};
  309 }
  310 
  311 #########################################################################
  312 
  313 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9