[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 5551 - (download) (as text) (annotate)
Tue Oct 2 20:48:05 2007 UTC (12 years, 2 months ago) by sh002i
File size: 9770 byte(s)
improved formatting for docs -- these were in pod sections but were all
formatted as verbatim sections, and i moved them into normal paragraphs,
lists, etc. should make things more readable from the web.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9