From 84b66adda38622f463709dfd0cefbe670588578d Mon Sep 17 00:00:00 2001 From: Nathan Wallach Date: Thu, 26 May 2022 16:50:59 +0300 Subject: [PATCH 1/2] Limit loop to compute factorial to n<=170, for n>170 return Perl Inf, as that would be the result of the calculation used for any such n due to overflow of the Perl floating point data type. --- lib/Parser/UOP/factorial.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Parser/UOP/factorial.pm b/lib/Parser/UOP/factorial.pm index 65600fec45..83b8866f28 100644 --- a/lib/Parser/UOP/factorial.pm +++ b/lib/Parser/UOP/factorial.pm @@ -25,6 +25,7 @@ sub _eval { my $n = shift; my $f = 1; $self->Error("Factorial can only be taken of (non-negative) integers") unless $n =~ m/^\d+$/; + return Inf if $n > 170; while ($n > 0) {$f *= $n; $n--} return $f; } From a25ced5ca3ab300e372d99b2b9c5d774f4866fbd Mon Sep 17 00:00:00 2001 From: Peter Staab Date: Fri, 27 May 2022 07:20:28 -0400 Subject: [PATCH 2/2] Factorial unit test (#1) * Added a test for the factorial. * FIX: clarified some of the language in the tests. --- lib/PGEnvironment.pm | 3 +- lib/Parser/UOP/factorial.pm | 2 +- t/math_objects/factorial.t | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 t/math_objects/factorial.t diff --git a/lib/PGEnvironment.pm b/lib/PGEnvironment.pm index c7bd9b793e..51c382c13e 100644 --- a/lib/PGEnvironment.pm +++ b/lib/PGEnvironment.pm @@ -62,7 +62,8 @@ sub new { # Load from the conf file. $self->{pg_dir} = $ENV{PG_ROOT}; - my $defaults_file = $self->{pg_dir} . "/conf/pg_defaults.yml"; + my $defaults_file = "$self->{pg_dir}/conf/pg_defaults.yml"; + $defaults_file = "$self->{pg_dir}/conf/pg_defaults.dist.yml" unless -r $defaults_file; die "Cannot read the configuration file $defaults_file" unless -r $defaults_file; my $options = LoadFile($defaults_file); diff --git a/lib/Parser/UOP/factorial.pm b/lib/Parser/UOP/factorial.pm index 83b8866f28..a5f8add846 100644 --- a/lib/Parser/UOP/factorial.pm +++ b/lib/Parser/UOP/factorial.pm @@ -25,7 +25,7 @@ sub _eval { my $n = shift; my $f = 1; $self->Error("Factorial can only be taken of (non-negative) integers") unless $n =~ m/^\d+$/; - return Inf if $n > 170; + return $self->Package("Infinity")->new() if $n > 170; while ($n > 0) {$f *= $n; $n--} return $f; } diff --git a/t/math_objects/factorial.t b/t/math_objects/factorial.t new file mode 100644 index 0000000000..5342054ad5 --- /dev/null +++ b/t/math_objects/factorial.t @@ -0,0 +1,78 @@ +use warnings; +use strict; + +package main; + +use Test::More; +use Test::Exception; + +# The following needs to include at the top of any testing down to END OF TOP_MATERIAL. + +BEGIN { + die 'PG_ROOT not found in environment.\n' unless $ENV{PG_ROOT}; + $main::pg_dir = $ENV{PG_ROOT}; +} + +use lib "$main::pg_dir/lib"; + +require("$main::pg_dir/t/build_PG_envir.pl"); + +## END OF TOP_MATERIAL + +use Parser; + +loadMacros('MathObjects.pl'); + +Context('Numeric'); +Context()->variables->add(y => "Real"); +Context()->variables->add(n => "Real"); + +my $five_fact = Compute('5!'); + +use Data::Dumper; +print Dumper $five_fact->class; + +is($five_fact->class, 'Real', 'factorial: check class of object'); +is($five_fact->type, 'Number', 'factorial: check type of object'); + +ok(Value::isValue($five_fact), 'factorial: check if an object is a value'); +ok(Value::isNumber($five_fact), 'factorial: check if an object is a number'); +ok(Value::isReal($five_fact), 'factorial: check if a number is a real number'); +ok(!Value::isComplex($five_fact), 'factorial: check if an integer is complex'); +ok(!Value::isFormula($five_fact), 'factorial: check if a number is not a formula'); + +is($five_fact->value,120, 'factorial: 5! is 120'); +is(Compute("0!")->value, 1, 'factorial: 0! is 1'); + +note('The double factorial is not defined here.'); +my $four_double_fact = Compute("4!!")->value; +ok(6.2e+23 < $four_double_fact && $four_double_fact < 6.3e+23, 'factorial: 4!! is defined as (4!)!=24!' ); + +ok(Compute("170!") > 1e+306, 'factorial: 170! is large but not infinite.'); + +note('Tests for throwing exceptions.'); + +throws_ok { + Compute("(-1)!"); +} +qr/Factorial can only be taken of \(non-negative\) integers/, 'factorial: can\'t take factorial of negative integers.'; + +throws_ok { + Compute("1.5!"); +} +qr/Factorial can only be taken of \(non-negative\) integers/, 'factorial: can\'t take factorial of non-integer reals.'; + +note('Try taking factorials of variables'); +my $n_fact = Compute("n!"); +is($n_fact->class, "Formula", "factorial: n! is a Formula"); +is($n_fact->type, "Number", "factorial: n! has type is Number"); +is($n_fact->eval(n=>5), 120, 'factorial: n! evaluated at n=5 is correct.'); + +# check infinite values +note('Tests for infinite values'); + +my $large_fact = Compute('171!'); +my $inf = Compute('inf'); +is($large_fact->value, $inf, '171! is infinite.'); + +done_testing();