diff --git a/macros/contexts/contextExtensions.pl b/macros/contexts/contextExtensions.pl index c9f8700db..0000d8065 100644 --- a/macros/contexts/contextExtensions.pl +++ b/macros/contexts/contextExtensions.pl @@ -392,18 +392,24 @@ package context::Extensions::Super; # sub super { my ($self, $method, $context) = @_; - return $self->superClass($context)->can($method); + my $superclass = $self->superClass($context); + return unless $superclass; + return $superclass->can($method); } # # Get the super class name from the extension hash in the context # sub superClass { - my $self = shift; - my $class = ref($self) || $self; - my $name = $self->extensionContext; - my $data = (shift || $self->context)->{$name}; - my $op = $self->{bop} || $self->{uop}; + my $self = shift; + my $class = ref($self) || $self; + my $name = $self->extensionContext; + my $context = shift; + $context = $self->context unless defined $context; + return unless $context && ref($context); + my $data = $context->{$name}; + return unless $data && ref($data) eq 'HASH'; + my $op = $self->{bop} || $self->{uop}; return $op ? $data->{$op} : $data->{ substr($class, length($name) + 2) }; } @@ -460,6 +466,24 @@ sub extensionContext { return $class; } +# +# Trap any calls to super-class methods that aren't in the overridden +# class and pass them on to the original class. +# +sub AUTOLOAD { + our $AUTOLOAD; + my $self = shift; + my $class = $self->extensionContext; + my $method = (split(/::/, $AUTOLOAD))[-1]; + if (substr($AUTOLOAD, 0, length($class) + 2) eq $class . '::') { + my $code = $self->super($method); + return &$code($self, @_) if $code; + } + my ($pkg, $file, $line, $subname) = caller(0); + die "Can't locate object method \"$method\" via package \"" . ref($self) . '"' . " at line $line of $file\n" + unless $method eq 'DESTROY'; +} + ################################################################################################# ################################################################################################# diff --git a/macros/contexts/contextSignificantFigures.pl b/macros/contexts/contextSignificantFigures.pl index 50bd65da0..254ce13e4 100644 --- a/macros/contexts/contextSignificantFigures.pl +++ b/macros/contexts/contextSignificantFigures.pl @@ -23,13 +23,13 @@ =head1 DESCRIPTION where the latter context, you or the student are not allowed to perform any operations on any numbers. -This is primarily for decimal numbers and keep track of signficant figures. With the context loaded, +This is primarily for decimal numbers and keep track of significant figures. With the context loaded, a call to C will parse the number or string to keep track of significant figures. For example, $x = Real('10.45'); $y = Real('37.1834'); -and these numbers will have 6 and 4 signficant figures respectively. To query the number of significant +and these numbers will have 6 and 4 significant figures respectively. To query the number of significant figures, use the C method. For example, C<< $x->sigfigs >> will return 4. The standard arithmetic operations +, -, *, / are defined for these and the result will have the correct @@ -41,7 +41,7 @@ =head1 DESCRIPTION $x * $y; -returns C<106.4> (with only 4 signficant figures, since one of them only has four). +returns C<106.4> (with only 4 significant figures, since one of them only has four). Finally, we can also perform subtraction as in @@ -52,24 +52,24 @@ =head1 DESCRIPTION =head2 Significant Figure Rules -A reminder about signficant figures is that all non-zero digits are significant. The rule about a zero's -significance depends on where it is in a number. +A reminder about significant figures is that all non-zero digits are significant. The rule about a +zero's significance depends on where it is in a number. =over -=item * Zeros between any significant digits are signficant. The zeros in 12.0034 are significant. There are 6 -significant figures in this number. +=item * Zeros between any significant digits are significant. The zeros in 12.0034 are significant. +There are 6 significant figures in this number. -=item * Zeros to the left of a non-zero digit are not significant. The zeros in 0.00123 are not signficant. -There are 3 significant figures in this number. +=item * Zeros to the left of a non-zero digit are not significant. The zeros in 0.00123 are +not significant. There are 3 significant figures in this number. -=item * Zeros to right of the decimal point and to the right a non-zero digit are significant. The zeros in 12.3400 are -significant. There are 6 significant figures in this number. +=item * Zeros to right of the decimal point and to the right a non-zero digit are significant. +The zeros in 12.3400 are significant. There are 6 significant figures in this number. -=item * Zeros to the left of the decimal point and to the right of a non zero digit are not significant. The -zeros in 12300 are not significant. There are 3 significant figures in this number. However, the presesence of -a significant zero changes the rule. The zeros in 12300.0 are all significant because the rightmost 0 is -significant and therefore the other zeros are significant. +=item * Zeros to the left of the decimal point and to the right of a non zero digit are not +significant. The zeros in 12300 are not significant. There are 3 significant figures in this number. +However, the presence of a significant zero changes the rule. The zeros in 12300.0 are all +significant because the rightmost 0 is significant and therefore the other zeros are significant. =back @@ -88,8 +88,8 @@ =head2 Significant Figure Rules =head3 Setting the number of significant figures. -The number of significant figures can be set in two ways. The first, in creation of the number if the option -C is used. For example +The number of significant figures can be set in two ways. The first, in creation of the number if the +option C is used. For example Real('100', sigfigs => 2) @@ -108,8 +108,8 @@ =head3 Setting the number of significant figures. $x = Real(17.05); $p = Real(4, sigfigs => 'inf') * $x; -One can set the number of significant figures after a number has been created with the C method. For -example, +One can set the number of significant figures after a number has been created with the C method. +For example, $x = Real(12.3456); @@ -118,9 +118,9 @@ =head3 Setting the number of significant figures. =head2 Flags -There are two flags that give authors some control over messaging for near correct answers. Recall that -a correct answer in this context is only given when a student has correct number of significant figures and -the correct answer (to all digits). +There are two flags that give authors some control over messaging for near correct answers. Recall that +a correct answer in this context is only given when a student has correct number of significant figures +and the correct answer (to all digits). =over @@ -268,7 +268,7 @@ sub expFor { sub string { my ($self, $equation, $precedence) = @_; my $string = $self->format($self->{expForm} || $self->getFlag('alwaysExponentialForm') ? 'E' : 'f'); - $string =~ s/E(?:(-)|\+)0*(\d+)/x10^$1$2/; + $string =~ s/E(?:(-)|\+)0*(\d+)/ 'x10^' . (defined($1) ? $1 : '') . $2 /e; $string =~ s/\^-(.*)/^(-$1)/; return $string =~ m/x/ && $precedence ? "($string)" : $string; } @@ -276,12 +276,12 @@ sub string { sub TeX { my ($self, $equation, $precedence) = @_; my $tex = $self->format($self->{expForm} || $self->getFlag('alwaysExponentialForm') ? 'E' : 'f'); - $tex =~ s/E(?:(-)|\+)0*(\d+)/\\times 10^{$1$2}/; + $tex =~ s/E([-+])0*(\d+)/'\\times 10^{' . ($1 eq '-' ? '-' : '') . $2 . '}'/e; return $tex =~ m/\\times/ && $precedence ? "\\left($tex\\right)" : "{$tex}"; } # Format the number in $value in either 'E' (exponential form) or 'f' decimal form using -# $n signficant figures. +# $n significant figures. # Example: format('E', '123.456', 6) returns 1.23456E+02 # format('f', 1.23E-01, 3) returns '0.123'. @@ -301,7 +301,7 @@ sub format { return $value; } -# Redefine addition. The leftmost signifcant place in the result is needed to get the +# Redefine addition. The leftmost significant place in the result is needed to get the # correct value. sub add { @@ -311,7 +311,7 @@ sub add { return $self->new($value, sigfigs => main::max(0, $exp + $self->expFor($value, $exp)), computed => 1); } -# Redefine subtraction. The leftmost signifcant place in the result is needed to get the +# Redefine subtraction. The leftmost significant place in the result is needed to get the # correct value. sub sub { @@ -337,7 +337,7 @@ sub div { return $self->new($l->value / $r->value, sigfigs => main::min($l->{sigfigs}, $r->{sigfigs}), computed => 1); } -# Redefine powers. Record whether this is an integer power of 10 for use with exponetial form. +# Redefine powers. Record whether this is an integer power of 10 for use with exponential form. sub power { my ($self, $l, $r, $other) = Value::checkOpOrderWithPromote(@_); @@ -428,8 +428,6 @@ sub cmp_preprocess { # to the correct answer, but the incorrect number of significant figures. # If so, show a warning and given partial credit. -# use Data::Dumper; - sub cmp_postprocess { my ($self, $ansHash) = @_; @@ -444,8 +442,8 @@ sub cmp_postprocess { # Create Value::Real versions of the student and correct answer # and check if the numbers are within tolerance but the number of significant figures is not correct. - my $student_real = Value::Real->new($student->string); - my $correct_real = Value::Real->new($correct->string); + my $student_real = Value::Real->new($student->value); + my $correct_real = Value::Real->new($correct->value); if ($self->getFlag('partial_incorrect_sf') && $student_real == $correct_real @@ -572,7 +570,7 @@ sub checkExponentialForm { } } -# Copy the special properites used for exponential notation processing +# Copy the special properties used for exponential notation processing sub COPY { my ($self, $from, $to) = @_; @@ -584,7 +582,7 @@ sub COPY { return $to; } -# Properly handle constants in exponential form, and add parenthese if needed +# Properly handle constants in exponential form, and add parentheses if needed sub STRING { my ($self, $fn, $precedence) = @_; @@ -594,7 +592,7 @@ sub STRING { return $string; } -# Properly handle constants in exponential form, and add parenthese if needed +# Properly handle constants in exponential form, and add parentheses if needed sub TEX { my ($self, $fn, $precedence) = @_; @@ -711,7 +709,7 @@ sub _eval { return $self->COPY($self, $self->SUPER::_eval(@_)->with(hadPlus => 1)); } -# Override Parser::BOP::mulitoply to handle the formation of an exponential form +# Override Parser::BOP::multiply to handle the formation of an exponential form package context::SignificantFigures::BOP::multiply; our @ISA = ('Parser::BOP::multiply', 'context::SignificantFigures::common'); @@ -765,7 +763,7 @@ sub _check { # $self->{def} = { %{ $self->{def} }, string => 'x', TeX => '\times' }; # - # Remove the variable from the expression if this was the only occurrance of 'x'. + # Remove the variable from the expression if this was the only occurrence of 'x'. # my $equation = $self->{equation}; $equation->{vCount}{x}--; @@ -788,7 +786,7 @@ sub TeX { return $self->TEX(sub { $self->SUPER::TeX }, @_); } -# Override Parser::BOP::power to mark occurances of ten-to-a-power so we can +# Override Parser::BOP::power to mark occurrences of ten-to-a-power so we can # recognize them when checking for exponential form package context::SignificantFigures::BOP::power; diff --git a/t/contexts/significant_figures.t b/t/contexts/significant_figures.t index 8385388a7..934476d96 100644 --- a/t/contexts/significant_figures.t +++ b/t/contexts/significant_figures.t @@ -28,6 +28,7 @@ sub expon { context::SignificantFigures::Real->expFor($_[0], 0) } sub FORMAT { context::SignificantFigures::Real->format(@_) } subtest 'Test the helper functions.' => sub { + is ROUND(1.335, 3), '1.34', 'Round 1.335 to 3 sig figs.'; is ROUND(12.34567, 5), '12.346', 'Round 12.346 to 5 sig figs.'; is ROUND(1.005, 3), '1.01', 'Check that rounding up is working with non-perfect values'; @@ -53,6 +54,7 @@ subtest 'Test the helper functions.' => sub { }; subtest 'Create numbers with significant digits using Real' => sub { + ok my $a1 = Real('0012.34'), 'Creating the number 0012.34'; is $a1->sigfigs, 4, '0012.34 has 4 significant figures.'; is $a1->E, 1, '0012.34 written as 1.234 * 10^1'; @@ -170,6 +172,7 @@ subtest 'Create numbers with significant digits using Real' => sub { }; subtest 'Create numbers by specifying significant figures' => sub { + my $a1 = Real(2, sigfigs => 3); is $a1->format('E'), '2.00E+00', '2 with 3 sig figs is 2.00'; is $a1->string, '2.00', 'Correct string output.'; @@ -188,6 +191,7 @@ subtest 'Create numbers by specifying significant figures' => sub { }; subtest 'Check for out of bounds significant figures' => sub { + my $a1 = Real('1'); like dies { Real('5', sigfigs => 20); }, qr/The number of significant figures must be less than 16/, 'Try to create a real with more than 16 sigfigs.'; @@ -206,6 +210,7 @@ subtest 'Check for out of bounds significant figures' => sub { }; subtest 'Creating numbers with significant figures using Compute' => sub { + my $a1 = Compute('12.345'); is $a1->format('E'), '1.2345E+01', 'Ensure that the internal storage of 12.345 is correct.'; is $a1->string, '12.345', 'Ensure that the string output of 12.345 is correct.'; @@ -369,7 +374,6 @@ subtest 'Subtracting two constants' => sub { is $a11->format('E'), '1.02E+00', '1.03 - .005 = 1.02 (check format)'; is $a11->sigfigs, 3, '1.03 - .005 = 1.02 (check sigfigs)'; is $a11->E, 0, '1.03 - .005 = 1.02 (check exp)'; - }; subtest 'Division' => sub { @@ -386,15 +390,12 @@ subtest 'Division' => sub { }; subtest 'Significant Figures for integers' => sub { - # my $a1 = Compute('1.0 * 10^2'); my $a1 = Compute('1.0E+02'); is $a1->format('E'), '1.0E+02', '1.0 * 10^2 internally is 1.0E+02'; }; subtest 'Significant Figures for partial credit' => sub { - # test an actual problem - my $source = <<~ 'END_SOURCE'; DOCUMENT(); loadMacros("PGstandard.pl","PGML.pl",'contextSignificantFigures.pl'); @@ -416,8 +417,6 @@ subtest 'Significant Figures for partial credit' => sub { ), 'source string renders'; - # print Dumper $pg->{result}; - is $pg->{result}{score}, 1, 'correct answer is scored correctly'; my $pg2 = WeBWorK::PG->new( diff --git a/t/contexts/significant_figures_units.t b/t/contexts/significant_figures_units.t new file mode 100644 index 000000000..67280ec1d --- /dev/null +++ b/t/contexts/significant_figures_units.t @@ -0,0 +1,46 @@ +#!/usr/bin/env perl + +=head1 SignificantFigure context + +Test the SignificantFigure context defined in contextSignificantFigure.pl. + +=cut + +use Test2::V0 '!E', { E => 'EXISTS' }; + +die "PG_ROOT not found in environment.\n" unless $ENV{PG_ROOT}; +do "$ENV{PG_ROOT}/t/build_PG_envir.pl"; + +use lib "$ENV{PG_ROOT}/lib"; + +# load the Units module so that %Units::known_units is populated +use Units; +use Value; +require Parser::Legacy; +import Parser::Legacy; + +loadMacros('contextSignificantFigures.pl', 'contextUnits.pl'); + +my $context = context::Units::extending("SignificantFigures")->withUnitsFor('length'); + +subtest 'Setup a basic Unit context extending SignificantFigures' => sub { + Context($context); # make it current without copying + ok(defined $context && ref($context), 'Got a context object'); + is $context->{name}, 'Units-SignificantFigures', 'Context has correct name'; +}; + +subtest 'Test a number with length units and significant figures' => sub { + Context($context); + ok my $a = Compute("123.0 cm"), 'able to compute with unit'; + + is $a, '123.0 cm', 'Value stringifies with units and sig figs'; + ok $a == Compute('1.230 m'), 'Value stringifies with correct unit conversion and sig figs'; + ok $a != Compute('123 cm'), 'Value does not lose significant figure information when stringified'; + + ok $a == Compute('4.035 ft'), 'Value in feet'; + ok $a == Compute('4.034 ft'), 'Value in feet (a little off, but when converted to m is correct)'; + ok $a == Compute('4.036 ft'), 'Value in feet (a little off, but when converted to m is correct)'; + +}; + +done_testing;