Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions macros/contexts/contextExtensions.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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) };
}

Expand Down Expand Up @@ -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';
}

#################################################################################################
#################################################################################################

Expand Down
74 changes: 36 additions & 38 deletions macros/contexts/contextSignificantFigures.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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<Real> 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<sigfigs> method. For example, C<< $x->sigfigs >> will return 4.

The standard arithmetic operations +, -, *, / are defined for these and the result will have the correct
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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<sigfigs> 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<sigfigs> is used. For example

Real('100', sigfigs => 2)

Expand All @@ -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<sigfigs> method. For
example,
One can set the number of significant figures after a number has been created with the C<sigfigs> method.
For example,

$x = Real(12.3456);

Expand All @@ -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

Expand Down Expand Up @@ -268,20 +268,20 @@ 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;
}

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'.
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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(@_);
Expand Down Expand Up @@ -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) = @_;

Expand All @@ -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
Expand Down Expand Up @@ -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) = @_;
Expand All @@ -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) = @_;
Expand All @@ -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) = @_;
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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}--;
Expand All @@ -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;
Expand Down
11 changes: 5 additions & 6 deletions t/contexts/significant_figures.t
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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.';
Expand All @@ -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.';
Expand All @@ -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.';
Expand Down Expand Up @@ -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 {
Expand All @@ -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');
Expand All @@ -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(
Expand Down
46 changes: 46 additions & 0 deletions t/contexts/significant_figures_units.t
Original file line number Diff line number Diff line change
@@ -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;
Loading