The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

use strict;
use Math::Symbolic qw(:all);
use Test::Simple 'no_plan';
# constant expressions
my %const_expr = (
'-0/1' => 0,
'0/1' => 0,
'(0+(8/13))+(5/13)' => 1,
'-10/5' => -2,
'10/5' => 2,
'-1+(1/2)' => -0.5,
'-1-(1/2)' => -1.5,
'1+(1/2)' => 1.5,
'1-(1/2)' => 0.5,
'11/2' => 5.5,
'(11/2)*2' => 11,
'-11/-22' => 0.5,
'-11/5' => -2.2,
'11/5' => 2.2,
'(1/2)-1' => -0.5,
'-(1/2)-1' => -1.5,
'(1/2)+(1/2)' => 1,
'(1/2)-(1/2)' => 0,
'-(1/2)+(-1/2)' => -1,
'-(1/2)-(1/-2)' => 0,
'(1/2)+(1/4)+(1/4)' => 1,
'(1/2)-(3/4)' => -0.25,
'-(1/2)-(3/-4)' => 0.25,
'-1*2*3*4*5*6*7*8*9' => -362880,
'-1+2*3+4*5+6*7+8*9' => 139,
'-1+2+3+4-5+6+7+8+9' => 33,
'-1+2-3+4-5+6-7+8-9' => -5,
'-1-2-3-4-5-6-7-8-9' => -45,
'-1/2/-3/4/-5/6/-7/-8/9' => -2.75573192239859e-06,
'1*2*3*4*5*6*7*8*9' => 362880,
'1+2*3+4*5+6*7+8*9' => 141,
'1+2+3+4+5+6+7+8+9' => 45,
'1+2-3+4-5+6-7+8-9' => -3,
'1-2-3-4-5-6-7-8-9' => -43,
'1/2/3/4/5/6/7/8/9' => 2.75573192239859e-06,
'(1/4)/(1/2)' => 0.5,
'-(1/4)/(1/-2)' => 0.5,
'(1/4)*(1*2*3)' => 1.5,
'-(1/4)*(1*2*3)' => -1.5,
'-(1/4)*(1*-2*3)' => 1.5,
'(1/4)*(1/3)*(1/2)*5' => 0.208333333333333,
'-(1/4)*(1/3)*(1/2)*5' => -0.208333333333333,
'-1/-7' => 0.142857142857143,
'-1/7' => -0.142857142857143,
'1/-7' => -0.142857142857143,
'1/7' => 0.142857142857143,
'2001/3000' => 0.667,
'2/1' => 2,
'2*(10/2)' => 10,
'-2*(1/-4)' => 0.5,
'2*(1/4)' => 0.5,
'(-2/-3)-(1/6)' => 0.5,
'(-2/3)-(1/6)' => -0.833333333333333,
'(2/-3)-(1/6)' => -0.833333333333333,
'(2/3)-(-1/-6)' => 0.5,
'(2/3)-(-1/6)' => 0.833333333333333,
'(2/3)-(1/-6)' => 0.833333333333333,
'(2/3)-(1/6)' => 0.5,
'(2/4)+(1/4)' => 0.75,
'(2/4)-(2/8)' => 0.25,
'(2*4+3^3)-(9+8+7+6+5+4+3+2+1)' => -10,
'24/36' => 0.666666666666667,
'(2*4)-(6*3)' => -10,
'(2*4)-(6-3)' => 5,
'(2*4)-(9+8+7+6+5+4+3+2+1)' => -37,
'2740/8220' => 0.333333333333333,
'3*((10+12)/6)' => 11,
'(3/10)/(18/25)' => 0.416666666666667,
'-3*(1/4)' => -0.75,
'3*(1/4)' => 0.75,
'(3*3)/(3-1)' => 4.5,
'36/30' => 1.2,
'(4/5)*(2/3)' => 0.533333333333333,
'45/75' => 0.6,
'-5/1' => -5,
'5/1' => 5,
'-5/10' => -0.5,
'5/-10' => -0.5,
'-7/4' => -1.75,
'7/4' => 1.75,
'(8+3^3)-(9+8+7+6+5+4+3+2+1)' => -10,
'8-(6*3)' => -10,
'8-(6-3)' => 5,
'8-(9+8+7+6+5+4+3+2+1)' => -37,
'-cos(10/5)' => 0.416146836547142,
'cos(10/5)' => -0.416146836547142,
'-cos(11/5)' => 0.588501117255346,
'cos(11/5)' => -0.588501117255346,
'-cos(-1/-7)' => -0.989813260446615,
'-cos(-1/7)' => -0.989813260446615,
'-cos(1/-7)' => -0.989813260446615,
'cos(-1/-7)' => 0.989813260446615,
'cos(-1/7)' => 0.989813260446615,
'cos(1/-7)' => 0.989813260446615,
'-cos(5/1)' => -0.283662185463226,
'cos(5/1)' => 0.283662185463226,
'-cos(7/4)' => 0.178246055649492,
'cos(7/4)' => -0.178246055649492,
'sqrt(4)*sqrt(9)' => 6,
'sqrt(-4)*sqrt(-9)' => -6,
'10^100 + 1 - 10^100' => 1,
'(5 ^ 104) / (5 ^ 102)' => 25,
'(10 ^ 199) - (10 ^ 199)' => 0,
'((10 ^ (-199)) + 1) - (10 ^ (-199))' => 1,
'(10 ^ 199) / (10 ^ 198)' => 10,
'5 ^ 4' => 625,
'((6 ^ 199) + 1) - (6 ^ 199)' => 1,
'1 * 10^-300 * 10^300' => 1,
'(10^100 + 1) - 10^100 + 1' => 2,
);
TEST_CONSTANTS: while ( my ($test, $ans) = each %const_expr ) {
my $f1 = parse_from_string($test);
# can the parser parse the test string?
ok( defined($f1), "parsing test string [$test]" );
if (!defined $f1) {
next TEST_CONSTANTS;
}
my $f2 = $f1->to_collected();
ok( defined($f2), "to_collected() returns defined [$f2] ($test)" );
if (!defined $f2) {
next TEST_CONSTANTS;
}
my $val = $f2->value();
ok( sprintf("%.11f", $val) == sprintf("%.11f", $ans), "Evaluation matches pre-calculated answer to 11 dp ($test)" );
}
# variable expressions
my %var_expr = (
'((((1/5)*(x+(z*y)))/2)*(((1/5)*(x+(z*y)))/3))/4' => [1,2,3,4,5,6,7,8,9],
'(((x+y)/2)*((x+y)/3))/(x+y)' => [1,2,3,4,5,6,7,8,9],
'(((x+y)/2)*((x+y)/3))/4' => [1,2,3,4,5,6,7,8,9],
'((1/(3*p))-(1/(3*q)))/((p/q)-(q/p))' => [1,2,3,4,5,6,7,8,9],
'((1/2)*x^2 + 2*x + (1/4))*(x+z+y)' => [1,2,3,4,5,6,7,8,9],
'((1/2)*x^2 + 2*x + (1/4))*2' => [1,2,3,4,5,6,7,8,9],
'((1/2)*x^2 + 2*x + (1/4))*x^2' => [1,2,3,4,5,6,7,8,9],
'((1/4)+(1/2))*3*x' => [1,2,3,4,5,6,7,8,9],
'((x*(2/14))+(x*y))*y' => [1,2,3,4,5,6,7,8,9],
'((x+y)^2)/7' => [1,2,3,4,5,6,7,8,9],
'((x+y)^p) * ((x+y)^q)' => [1,2,3,7,8,9],
'((x+y+z)*x^2)/5' => [1,2,3,4,5,6,7,8,9],
'((y/x)*z)+((y/x)*y)' => [1,2,3,4,5,6,7,8,9],
'(-12*y+36)/-8' => [1,2,3,4,5,6,7,8,9],
'(1+2+x)*((y+z)*(p+q+c))' => [1,2,3,4,5,6,7,8,9],
'(1+2+x)*((y+z)*(p-q-c))' => [1,2,3,4,5,6,7,8,9],
'(1+2+x)*(sin(y)+z)' => [1,2,3,4,5,6,7,8,9],
'(1+2+x)*(y+z)' => [1,2,3,4,5,6,7,8,9],
'(1/(x+1))+(x/(4-x))-(1/(x-2))' => [1,2,3,4,5,6,7,9],
'(1/14)*(x+x+y)' => [1,2,3,4,5,6,7,8,9],
'(1/4)*2+(x/2)' => [1,2,3,4,5,6,7,8,9],
'(1/sqrt(x))*(1/sqrt(x))' => [1,2,3,7,8,9],
'(21*x+9)/15' => [1,2,3,4,5,6,7,8,9],
'(3*(x^2))/(x^3)' => [1,2,3,4,5,6,7,8,9],
'(5*x)/(1-(1/x))' => [1,2,3,4,5,6,8,9],
'(5*z-30)/-5' => [1,2,3,4,5,6,7,8,9],
'(5+y)*x^(2-z)' => [1,2,3,7,8,9],
'(5+y)*x^(2-z)*(5+y)' => [1,2,3,7,8,9],
'(5+y)*x^2' => [1,2,3,4,5,6,7,8,9],
'(5+y)*x^2*(5+y)' => [1,2,3,4,5,6,7,8,9],
'(5/(x+1))-((x-2)/(x+1))' => [1,2,3,4,5,6,7,8,9],
'(6*x+4)/2' => [1,2,3,4,5,6,7,8,9],
'(p*(q+c))/((q+c)*p)' => [1,2,3,4,5,6,7,8,9],
'(p-p^2)/(3*p^3-3*p)' => [1,2,3,4,5,6,7,8,9],
'(sin(x)*q + sin(x)*c)/(sin(x)*p + sin(x)*d)' => [1,2,4,5,7,8,9],
'(sinh(sin(x)+cos(y))+cosh(sin(x)+cos(y)))*(sinh(x)-cosh(y))' => [1,2,3,4,5,6,7,8,9],
'(sinh(x)+cosh(y))*(sinh(x)+cosh(y))' => [1,2,3,4,5,6,7,8,9],
'(sinh(x)+cosh(y))*(sinh(x)-cosh(y))' => [1,2,3,4,5,6,7,8,9],
'(x*p-x*q)/(x*p+x*q)' => [1,2,3,4,5,6,7,8,9],
'(x+y)*(((x+y)/14) + ((x+y)/12))' => [1,2,3,4,5,6,7,8,9],
'(x+y+z)*x' => [1,2,3,4,5,6,7,8,9],
'(x+y+z)*x^2' => [1,2,3,4,5,6,7,8,9],
'(x+y+z)/2' => [1,2,3,4,5,6,7,8,9],
'(x^2+y^2)/(z^2+p^2)' => [1,2,3,4,5,6,7,8,9],
'(x^p)^(q)' => [1,2,3,7,8,9],
'(y+x)*(x+2*x)*(1+(y*x))' => [1,2,3,4,5,6,7,8,9],
'-((((1/5)*(x+(z*y)))/2)*(((1/5)*(x+(z*y)))/3))/4' => [1,2,3,4,5,6,7,8,9],
'-((((1/5)*(x-(z*y)))/2)*(((1/-5)*(x+(z*y)))/3))/4' => [1,2,3,4,5,6,7,8,9],
'-(((x+y)/2)*((x+y)/3))/(x+y)' => [1,2,3,4,5,6,7,8,9],
'-(((x+y)/2)*((x+y)/3))/4' => [1,2,3,4,5,6,7,8,9],
'-(((x+y)/2)*((x-y)/3))/-4' => [1,2,3,4,5,6,7,8,9],
'-((1/2)*x^2 + 2*x + (1/4))*x^2' => [1,2,3,4,5,6,7,8,9],
'-((1/2)*x^2 + 2*x - (1/4))*2' => [1,2,3,4,5,6,7,8,9],
'-((1/2)*x^2 - 2*x + (1/4))*(x+z+y)' => [1,2,3,4,5,6,7,8,9],
'-((x*(2/14))+(x*y))*y' => [1,2,3,4,5,6,7,8,9],
'-((x+y)^2)/-7' => [1,2,3,4,5,6,7,8,9],
'-((x-y+z)*x^2)/5' => [1,2,3,4,5,6,7,8,9],
'-((y/x)*z)+((y/x)*y)' => [1,2,3,4,5,6,7,8,9],
'-(1+2+x)*((y+z)*(p+q+c))' => [1,2,3,4,5,6,7,8,9],
'-(1+2+x)*((y+z)*(p-q-c))' => [1,2,3,4,5,6,7,8,9],
'-(1+2+x)*(sin(y)-z)' => [1,2,3,4,5,6,7,8,9],
'-(1-2+x)*(y-z)' => [1,2,3,4,5,6,7,8,9],
'-(1/14)*(x+x+y)' => [1,2,3,4,5,6,7,8,9],
'-(1/4)*(1/3)*(1/2)*5' => [1,2,3,4,5,6,7,8,9],
'-(5/(x+1))-((x-2)/(x-1))' => [1,2,3,4,5,6,8,9],
'-(sinh(sin(x)-cos(y))+cosh(sin(x)-cos(y)))*(sinh(x)-cosh(y))' => [1,2,3,4,5,6,7,8,9],
'-(sinh(x)+cosh(y))*(sinh(x)-cosh(y))' => [1,2,3,4,5,6,7,8,9],
'-(sinh(x)-cosh(y))*(sinh(x)+cosh(y))' => [1,2,3,4,5,6,7,8,9],
'-(x*p-x*q)/(x*p-x*q)' => [1,2,3,4,5,6,7,8,9],
'-(x+y)*(((x+y)/14) + ((x+y)/12))' => [1,2,3,4,5,6,7,8,9],
'-(x+y)*(((x-y)/14) + ((x+y)/12))' => [1,2,3,4,5,6,7,8,9],
'-(x+y-z)*x^2' => [1,2,3,4,5,6,7,8,9],
'-(x-y-z)*x' => [1,2,3,4,5,6,7,8,9],
'-(x^2+y^2)/(z^2+p^2)' => [1,2,3,4,5,6,7,8,9],
'-(y+x)*(x+2*x)*(1+(y*x))' => [1,2,3,4,5,6,7,8,9],
'-1*(x+1)*(x+1)+1' => [1,2,3,4,5,6,7,8,9],
'-2/(-2*x)' => [1,2,3,4,5,6,7,8,9],
'-2/-x' => [1,2,3,4,5,6,7,8,9],
'-cos(((x+y)^2)/7)' => [1,2,3,4,5,6,7,8,9],
'-cos((1+2+x)*((y+z)*(p-q-c)))' => [1,2,3,4,5,6,7,8,9],
'-cos((1+2+x)*((y-z)*(p+q+c)))' => [1,2,3,4,5,6,7,8,9],
'-cos((1+2-x)*(sin(y)+z))' => [1,2,3,4,5,6,7,8,9],
'-cos((1-2+x)*(y+z))' => [1,2,3,4,5,6,7,8,9],
'-cos(-1/-7)' => [1,2,3,4,5,6,7,8,9],
'-cos(-1/7)' => [1,2,3,4,5,6,7,8,9],
'-cos(1/-7)' => [1,2,3,4,5,6,7,8,9],
'-cos(10/5)' => [1,2,3,4,5,6,7,8,9],
'-cos(11/5)' => [1,2,3,4,5,6,7,8,9],
'-cos(5/1)' => [1,2,3,4,5,6,7,8,9],
'-cos(7/4)' => [1,2,3,4,5,6,7,8,9],
'-cos(x^2+y^2)' => [1,2,3,4,5,6,7,8,9],
'-sin(x)*(x+y+z)' => [1,2,3,4,5,6,7,8,9],
'-x^2-y^2' => [1,2,3,4,5,6,7,8,9],
'1/(1-(1/x))' => [1,2,3,4,5,6,8,9],
'2/-x' => [1,2,3,4,5,6,7,8,9],
'4*x*(1/4)' => [1,2,3,4,5,6,7,8,9],
'5*x^(3+z)' => [1,2,3,7,8,9],
'5*x^(3+z)*5' => [1,2,3,7,8,9],
'5*x^3' => [1,2,3,4,5,6,7,8,9],
'6*x^3*5' => [1,2,3,4,5,6,7,8,9],
'cos(((x+y)^2)/7)' => [1,2,3,4,5,6,7,8,9],
'cos((1+2+x)*((y+z)*(p+q+c)))' => [1,2,3,4,5,6,7,8,9],
'cos((1+2+x)*((y+z)*(p-q-c)))' => [1,2,3,4,5,6,7,8,9],
'cos((1+2+x)*(sin(y)+z))' => [1,2,3,4,5,6,7,8,9],
'cos((1+2+x)*(y+z))' => [1,2,3,4,5,6,7,8,9],
'cos(-1/-7)' => [1,2,3,4,5,6,7,8,9],
'cos(-1/7)' => [1,2,3,4,5,6,7,8,9],
'cos(1/-7)' => [1,2,3,4,5,6,7,8,9],
'cos(10/5)' => [1,2,3,4,5,6,7,8,9],
'cos(11/5)' => [1,2,3,4,5,6,7,8,9],
'cos(5/1)' => [1,2,3,4,5,6,7,8,9],
'cos(7/4)' => [1,2,3,4,5,6,7,8,9],
'cos(x^2+y^2)' => [1,2,3,4,5,6,7,8,9],
'sin((x+4)*(x+1))*sin((x+1)*(x+4))' => [1,2,3,4,5,6,7,8,9],
'sin(x)*(x+y+z)' => [1,2,3,4,5,6,7,8,9],
'sqrt(x)*sqrt(x)' => [1,2,3,7,8,9],
'sqrt(x)*sqrt(x)*sqrt(x)' => [1,2,3,7,8,9],
'sqrt(x)*sqrt(x)*sqrt(x)*sqrt(x)' => [1,2,3,7,8,9],
'x/x' => [1,2,3,4,5,6,7,8,9],
'x^(-2)' => [1,2,3,4,5,6,7,8,9],
'x^(2-z)*(5+y)' => [1,2,3,7,8,9],
'x^(3+z)*5' => [1,2,3,7,8,9],
'x^(4*(x+y))' => [1,2,3,7,8,9],
'x^-3 * x^2' => [1,2,3,4,5,6,7,8,9],
'x^2 / x' => [1,2,3,4,5,6,7,8,9],
'x^2*(5+y)' => [1,2,3,4,5,6,7,8,9],
'x^2+4*x+4+((13*x+7)/(x^2-x-2))' => [1,2,3,4,5,6,7,9],
'x^2+y^2' => [1,2,3,4,5,6,7,8,9],
'x^3*5' => [1,2,3,4,5,6,7,8,9],
);
my %test_vectors = (
1 => [0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1],
2 => [0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
3 => [0.1, 0.3, 0.5, 0.7, 0.9, 1.1],
4 => [-0.5, -0.6, -0.7, -0.8, -0.9, -1, -1.1],
5 => [-0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1],
6 => [0.1, -0.3, -0.5, -0.7, -0.9, -1.1],
7 => [1.0, 0.9, 0.8, 0.7, 0.8, 0.9, 1],
8 => [2.0, 1.9, 1.8, 1.7, 1.8, 1.9, 2],
9 => [0.1, 1.7, 0.4, 0.02, 1.8, -0.9, 1],
);
TEST_VARIABLES: while ( my ($test, $valid_tests) = each %var_expr ) {
my $f1 = parse_from_string($test);
ok( defined($f1), "parsing test string [$test]" );
if (!defined $f1) {
next TEST_VARIABLES;
}
my $f2 = $f1->to_collected();
ok( defined($f2), "to_collected() returns defined [$f2] ($test)" );
if (!defined $f2) {
next TEST_VARIABLES;
}
# test for numerical equivalence with a known set of test vectors
# Some expressions aren't valid for some input vectors
foreach my $tv (@{$valid_tests}) {
my ($x, $y, $z, $p, $q, $c, $d) = @{$test_vectors{$tv}};
my $v1 = $f1->value( 'x' => $x, 'y' => $y, 'z' => $z, 'p' => $p, 'q' => $q, 'c' => $c, 'd' => $d );
my $v2 = $f2->value( 'x' => $x, 'y' => $y, 'z' => $z, 'p' => $p, 'q' => $q, 'c' => $c, 'd' => $d );
ok( sprintf("%.11f", $v1) == sprintf("%.11f", $v2), "[$f1]vs[$f2]/[$v1]vs[$v2] numerically equivalent to 11 dp with test $tv");
}
}