package.xml0000644000000000000000000001615314552656040011706 0ustar rootroot decimal pecl.php.net Arbitrary precision decimal arithmetic Correctly-rounded arbitrary precision decimal floating-point arithmetic Rudi Theunissen rtheunissen rtheunissen@php.net yes 2024-01-20 1.5.0 1.5.0 stable stable MIT License - Fix object handlers for PHP 8.3 (thank you @Majkl578) 7.0.0 1.4.0b1 decimal decimal-1.5.0/tests/php7/methods/__construct.phpt0000644000000000000000000000773414552656040020535 0ustar rootroot--TEST-- Decimal::__construct --SKIPIF-- = 80000) echo "skip"; ?> --FILE-- $test) { list($args, $precision, $expect) = $test; $result = new Decimal(...$args); if ($result->precision() !== $precision || (string) $result !== $expect) { var_dump(compact("index", "args", "result", "precision", "expect")); } } try { new Decimal(" 1"); } catch (DomainException $e) { printf("A %s\n", $e->getMessage()); } try { new Decimal("1 "); } catch (DomainException $e) { printf("B %s\n", $e->getMessage()); } try { new Decimal(1.5); } catch (TypeError $e) { printf("C %s\n", $e->getMessage()); } try { new Decimal(null); } catch (TypeError $e) { printf("D %s\n", $e->getMessage()); } try { new Decimal(0, "b"); } catch (TypeError $e) { printf("E %s\n", $e->getMessage()); } try { new Decimal(0, null); } catch (TypeError $e) { printf("F %s\n", $e->getMessage()); } try { new Decimal(0, 0); } catch (OutOfRangeException $e) { printf("G %s\n", $e->getMessage()); } try { new Decimal(0, -1); } catch (OutOfRangeException $e) { printf("H %s\n", $e->getMessage()); } /* Check max precision */ try { new Decimal(0, Decimal::MAX_PRECISION + 1); } catch (OutOfRangeException $e) { printf("I %s\n", $e->getMessage()); } try { (new Decimal())->__construct(); } catch (BadMethodCallException $e) { printf("J %s\n", $e->getMessage()); } ?> --EXPECTF-- Warning: Loss of data on integer conversion in %s on line %d Warning: Loss of data on string conversion in %s on line %d A Failed to parse string as decimal: " 1" B Failed to parse string as decimal: "1 " C Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, float given D Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, null given E Argument 2 passed to Decimal\Decimal::__construct() must be of the type int%s string given F Argument 2 passed to Decimal\Decimal::__construct() must be of the type int%s null given G Decimal precision out of range H Decimal precision out of range I Decimal precision out of range J Decimal objects are immutable decimal-1.5.0/tests/php7/methods/abs.phpt0000644000000000000000000000154014552656040016745 0ustar rootroot--TEST-- Decimal::abs --SKIPIF-- --FILE-- abs(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } /* Test that abs does not modify the original */ $number = decimal("-1"); $result = $number->abs(); if ((string) $number !== "-1") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/add.phpt0000644000000000000000000000636614552656040016743 0ustar rootroot--TEST-- Decimal::add --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $results = [ $op1->add($op2), $op1 + $op2, $op2 + $op1, ]; foreach ($results as $result) { if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); break; } } } /* Test that op does not modify the original */ $number = new Decimal("2"); $number->add(5); $number + 5; if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line 14 Warning: Loss of data on string conversion in %s on line 15 Warning: Loss of data on string conversion in %s on line 39 Warning: Loss of data on string conversion in %s on line 40 Warning: Loss of data on string conversion in %s on line 44 Warning: Loss of data on string conversion in %s on line 45 decimal-1.5.0/tests/php7/methods/avg.phpt0000644000000000000000000000364414552656040016764 0ustar rootroot--TEST-- Decimal::avg --SKIPIF-- --FILE-- $test) { list($args, $expect, $precision) = $test; $result = Decimal::avg(...$args); if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "args", "result", "expect", "precision")); } } /* Test non-traversable */ try { Decimal::avg("abc"); } catch (TypeError $e) { printf("%s\n", $e->getMessage()); } /* Test bad types */ try { Decimal::avg(["abc"]); } catch (DomainException $e) { printf("%s\n", $e->getMessage()); } ?> --EXPECT-- Decimal\Decimal::avg() expected parameter 1 to be an array or traversable object, string given Failed to parse string as decimal: "abc" decimal-1.5.0/tests/php7/methods/between.phpt0000644000000000000000000000634214552656040017636 0ustar rootroot--TEST-- Decimal::between --SKIPIF-- --FILE-- $test) { list($decimal, $opLeft, $opRight, $expect) = $test; $result = $decimal->between($opLeft, $opRight); if ($result !== $expect) { var_dump(compact("index", "decimal", "opLeft", "opRight", "expect", "result")); break; } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/ceil.phpt0000644000000000000000000000155214552656040017117 0ustar rootroot--TEST-- Decimal::ceil --SKIPIF-- --FILE-- ceil(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } /* Test that ceil does not modify the original */ $number = decimal("-1.5"); $result = $number->ceil(); if ((string) $number !== "-1.5") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/compareTo.phpt0000644000000000000000000001166014552656040020135 0ustar rootroot--TEST-- Decimal::compareTo --SKIPIF-- --FILE-- NAN], [decimal( "INF"), decimal( "INF"), INF <=> INF], [decimal( "INF"), decimal("-INF"), INF <=> -INF], [decimal("-INF"), decimal( "NAN"), -INF <=> NAN], [decimal("-INF"), decimal( "INF"), -INF <=> INF], [decimal("-INF"), decimal("-INF"), -INF <=> -INF], [decimal( "NAN"), decimal( "NAN"), NAN <=> NAN], [decimal( "NAN"), decimal( "INF"), NAN <=> INF], [decimal( "NAN"), decimal("-INF"), NAN <=> -INF], [decimal( "INF"), NAN, INF <=> NAN], [decimal( "INF"), INF, INF <=> INF], [decimal( "INF"), -INF, INF <=> -INF], [decimal("-INF"), NAN, -INF <=> NAN], [decimal("-INF"), INF, -INF <=> INF], [decimal("-INF"), -INF, -INF <=> -INF], [decimal( "NAN"), NAN, NAN <=> NAN], [decimal( "NAN"), INF, NAN <=> INF], [decimal( "NAN"), -INF, NAN <=> -INF], ]; foreach ($tests as $index => $test) { list($op1, $op2, $expect) = $test; $results = [ $op1->compareTo($op2), $op1 <=> $op2, ]; foreach ($results as $result) { if ($result !== $expect) { var_dump(compact("index", "op1", "op2", "result", "expect")); break; } } } /** * Check throws for undefined comparison */ try { decimal() <=> "abc"; } catch (DomainException $e) { printf("%s\n", $e->getMessage()); } try { decimal() <=> ""; } catch (DomainException $e) { printf("%s\n", $e->getMessage()); } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line %d decimal-1.5.0/tests/php7/methods/copy.phpt0000644000000000000000000000060714552656040017155 0ustar rootroot--TEST-- Decimal::copy --SKIPIF-- --FILE-- copy(); var_dump($src, $dst); ?> --EXPECT-- object(Decimal\Decimal)#1 (2) { ["value"]=> string(3) "123" ["precision"]=> int(32) } object(Decimal\Decimal)#2 (2) { ["value"]=> string(3) "123" ["precision"]=> int(32) } decimal-1.5.0/tests/php7/methods/div.phpt0000644000000000000000000000745614552656040016776 0ustar rootroot--TEST-- Decimal::div --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $results = [ $op1->div($op2), $op1 / $op2, ]; foreach ($results as $result) { if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); break; } } } /* Test that op does not modify the original */ $number = new Decimal("2"); $number->div(5); $number / 5; if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } /* Check division by zero */ try { new Decimal() / 0; } catch (DivisionByZeroError $e) { printf("A %s\n", $e->getMessage()); } try { new Decimal(1) / 0; } catch (DivisionByZeroError $e) { printf("B %s\n", $e->getMessage()); } try { new Decimal(NAN) / 0; } catch (DivisionByZeroError $e) { printf("C %s\n", $e->getMessage()); } try { new Decimal(INF) / 0; } catch (DivisionByZeroError $e) { printf("D %s\n", $e->getMessage()); } try { new Decimal(-INF) / 0; } catch (DivisionByZeroError $e) { printf("E %s\n", $e->getMessage()); } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line 9 Warning: Loss of data on string conversion in %s on line 10 Warning: Loss of data on string conversion in %s on line 14 Warning: Loss of data on string conversion in %s on line 15 Warning: Loss of data on string conversion in %s on line 19 Warning: Loss of data on string conversion in %s on line 20 Warning: Loss of data on string conversion in %s on line 24 Warning: Loss of data on string conversion in %s on line 25 A Division by zero B Division by zero C Division by zero D Division by zero E Division by zero decimal-1.5.0/tests/php7/methods/equals.phpt0000644000000000000000000001052714552656040017477 0ustar rootroot--TEST-- Decimal::equals --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect) = $test; $results = [ $op1 == $op2, $op2 == $op1, !($op1 != $op2), !($op2 != $op1), ]; if ($op1 instanceof Decimal) { $results[] = $op1->equals($op2); } if ($op2 instanceof Decimal) { $results[] = $op2->equals($op1); } foreach ($results as $result) { if ($result !== $expect) { var_dump(compact("op1", "op2", "result", "expect")); break; } } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/exp.phpt0000644000000000000000000000175614552656040017005 0ustar rootroot--TEST-- Decimal::exp --SKIPIF-- --FILE-- exp(); if ((string) $result !== $expect || $result->precision() !== $precision) { print_r(compact("number", "result", "expect", "precision")); } } /* Test that exp does not modify the original */ $number = decimal("2"); $result = $number->exp(); if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/floor.phpt0000644000000000000000000000174414552656040017327 0ustar rootroot--TEST-- Decimal::floor --SKIPIF-- --FILE-- add("0.7")->mul(10)->floor(), "8"] ]; foreach ($tests as $test) { $number = $test[0]; $expect = $test[1]; $result = decimal($number)->floor(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } /* Test that floor does not modify the original */ $number = decimal("2.5"); $result = $number->floor(); if ((string) $number !== "2.5") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/isEven.phpt0000644000000000000000000000221414552656040017430 0ustar rootroot--TEST-- Decimal::isEven --SKIPIF-- --FILE-- isEven(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line %d Warning: Loss of data on string conversion in %s on line %d decimal-1.5.0/tests/php7/methods/isInf.phpt0000644000000000000000000000156714552656040017261 0ustar rootroot--TEST-- Decimal::isINF --SKIPIF-- --FILE-- isINF(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/isInteger.phpt0000644000000000000000000000157214552656040020136 0ustar rootroot--TEST-- Decimal::isInteger --SKIPIF-- --FILE-- isInteger(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/isNaN.phpt0000644000000000000000000000157014552656040017213 0ustar rootroot--TEST-- Decimal::isNAN --SKIPIF-- --FILE-- isNAN(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/isOdd.phpt0000644000000000000000000000223214552656040017241 0ustar rootroot--TEST-- Decimal::isOdd --SKIPIF-- --FILE-- isOdd(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line %d Warning: Loss of data on string conversion in %s on line %d decimal-1.5.0/tests/php7/methods/isZero.phpt0000644000000000000000000000157314552656040017461 0ustar rootroot--TEST-- Decimal::isZero --SKIPIF-- --FILE-- isZero(); if ((string) $result !== (string) $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/ln.phpt0000644000000000000000000000376714552656040016626 0ustar rootroot--TEST-- Decimal::ln --SKIPIF-- --FILE-- ln(); if ((string) $result !== $expect || $result->precision() !== $precision) { print_r(compact("number", "result", "expect", "precision")); } } /* Test that log10 does not modify the original */ $number = decimal("2"); $result = $number->ln(); if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/log10.phpt0000644000000000000000000000350614552656040017126 0ustar rootroot--TEST-- Decimal::log10 --SKIPIF-- --FILE-- log10(); if ((string) $result !== $expect || $result->precision() !== $precision) { print_r(compact("number", "result", "expect", "precision")); } } /* Test that log10 does not modify the original */ $number = decimal("2"); $result = $number->log10(); if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/mod.phpt0000644000000000000000000000636414552656040016770 0ustar rootroot--TEST-- Decimal::mod --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $results = [ $op1->mod($op2), $op1 % $op2, ]; foreach ($results as $result) { if ([(string) $result, $result->precision()] !== [(string) $expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); break; } } } /* Test that op does not modify the original */ $number = decimal("2"); $number->mod(5); $number % 5; if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } /* Check division by zero */ try { decimal() % 0; } catch (DivisionByZeroError $e) { printf("A %s\n", $e->getMessage()); } try { decimal(1) % 0; } catch (DivisionByZeroError $e) { printf("B %s\n", $e->getMessage()); } try { decimal(NAN) % 0; } catch (DivisionByZeroError $e) { printf("C %s\n", $e->getMessage()); } try { decimal(INF) % 0; } catch (DivisionByZeroError $e) { printf("D %s\n", $e->getMessage()); } try { decimal(-INF) % 0; } catch (DivisionByZeroError $e) { printf("E %s\n", $e->getMessage()); } ?> --EXPECT-- A Division by zero B Division by zero C Division by zero D Division by zero E Division by zero decimal-1.5.0/tests/php7/methods/mul.phpt0000644000000000000000000000716214552656040017003 0ustar rootroot--TEST-- Decimal::mul --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $results = [ $op1->mul($op2), $op1 * $op2, $op2 * $op1, ]; foreach ($results as $result) { if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); break; } } } /* Test that op does not modify the original */ $number = new Decimal("2"); $number->mul(5); $number * 5; if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line 9 Warning: Loss of data on string conversion in %s on line 10 Warning: Loss of data on string conversion in %s on line 14 Warning: Loss of data on string conversion in %s on line 15 Warning: Loss of data on string conversion in %s on line 19 Warning: Loss of data on string conversion in %s on line 20 Warning: Loss of data on string conversion in %s on line 24 Warning: Loss of data on string conversion in %s on line 25 Warning: Loss of data on integer conversion in %s on line 77 Warning: Loss of data on integer conversion in %s on line 79 Warning: Loss of data on integer conversion in %s on line 80 decimal-1.5.0/tests/php7/methods/negate.phpt0000644000000000000000000000163214552656040017445 0ustar rootroot--TEST-- Decimal::negate --SKIPIF-- --FILE-- negate()); var_dump((string) decimal("-0")->negate()); var_dump((string) decimal( "0")->negate()); var_dump((string) decimal( "1")->negate()); var_dump((string) decimal("-1")->negate()); var_dump((string) decimal( "NAN")->negate()); var_dump((string) decimal( "INF")->negate()); var_dump((string) decimal("-INF")->negate()); /** * Check that negate does not modify the original */ $obj = decimal("1.234"); $obj->negate(); var_dump((string) $obj); ?> --EXPECT-- string(2) "-0" string(1) "0" string(2) "-0" string(2) "-1" string(1) "1" string(3) "NAN" string(4) "-INF" string(3) "INF" string(5) "1.234" decimal-1.5.0/tests/php7/methods/parity.phpt0000644000000000000000000000232314552656040017510 0ustar rootroot--TEST-- Decimal::parity --SKIPIF-- --FILE-- parity()); var_dump((string) decimal("0")->parity()); var_dump((string) decimal("1")->parity()); var_dump((string) decimal("2")->parity()); var_dump((string) decimal("3")->parity()); var_dump((string) decimal("-0")->parity()); var_dump((string) decimal("-1")->parity()); var_dump((string) decimal("-2")->parity()); var_dump((string) decimal("-3")->parity()); var_dump((string) decimal("0.5")->parity()); var_dump((string) decimal("1.5")->parity()); var_dump((string) decimal("2.5")->parity()); var_dump((string) decimal("3.5")->parity()); var_dump((string) decimal( "NAN")->parity()); var_dump((string) decimal( "INF")->parity()); var_dump((string) decimal("-INF")->parity()); ?> --EXPECT-- string(1) "0" string(1) "0" string(1) "1" string(1) "0" string(1) "1" string(1) "0" string(1) "1" string(1) "0" string(1) "1" string(1) "0" string(1) "1" string(1) "0" string(1) "1" string(1) "1" string(1) "1" string(1) "1" decimal-1.5.0/tests/php7/methods/pow.phpt0000644000000000000000000000327614552656040017015 0ustar rootroot--TEST-- Decimal::pow --SKIPIF-- --FILE-- pow(decimal($op2)), $op1->pow($op2), $op1 ** decimal($op2), $op1 ** $op2, ]; foreach ($results as $result) { if (!$result instanceof Decimal || (string) $result !== (string) $expect) { print_r(compact("op1", "op2", "result", "expect")); break; } } } ?> --EXPECTF-- decimal-1.5.0/tests/php7/methods/precision.phpt0000644000000000000000000000113714552656040020175 0ustar rootroot--TEST-- Decimal::precision --SKIPIF-- --FILE-- precision()); var_dump(decimal(5)->precision()); var_dump(decimal(5, 5)->precision()); decimal(0, 0); ?> --EXPECTF-- int(28) int(28) int(5) Fatal error: Uncaught OutOfRangeException: Decimal precision out of range in %s:%d Stack trace: #0 %s(%d): Decimal\Decimal->__construct(0, 0) #1 %s(%d): decimal(0, 0) #2 {main} thrown in %s on line %d decimal-1.5.0/tests/php7/methods/rem.phpt0000644000000000000000000000620414552656040016765 0ustar rootroot--TEST-- Decimal::rem --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $result = $op1->rem($op2); if ([(string) $result, $result->precision()] !== [(string) $expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); } } /* Test that op does not modify the original */ $number = decimal("2"); $number->rem(5); if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } /* Check division by zero */ try { decimal()->rem(0); } catch (DivisionByZeroError $e) { printf("A %s\n", $e->getMessage()); } try { decimal(1)->rem(0); } catch (DivisionByZeroError $e) { printf("B %s\n", $e->getMessage()); } try { decimal(NAN)->rem(0); } catch (DivisionByZeroError $e) { printf("C %s\n", $e->getMessage()); } try { decimal(INF)->rem(0); } catch (DivisionByZeroError $e) { printf("D %s\n", $e->getMessage()); } try { decimal(-INF)->rem(0); } catch (DivisionByZeroError $e) { printf("E %s\n", $e->getMessage()); } ?> --EXPECT-- A Division by zero B Division by zero C Division by zero D Division by zero E Division by zero decimal-1.5.0/tests/php7/methods/round.phpt0000644000000000000000000003400214552656040017326 0ustar rootroot--TEST-- Decimal::round --SKIPIF-- --FILE-- round($places, $mode); if ($result !== $expect) { print_r(compact("number", "places", "mode", "result", "expect")); } } } /* Test that round does not modify the original */ $number = new Decimal("1.2345"); $result = $number->round(); if ((string) $number !== "1.2345") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/shift.phpt0000644000000000000000000000240514552656040017316 0ustar rootroot--TEST-- Decimal::shift --SKIPIF-- --FILE-- shift($exponent); if ((string) $result !== $expect || $result->precision() !== 28) { print_r(compact("number", "exponent", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/signum.phpt0000644000000000000000000000146014552656040017503 0ustar rootroot--TEST-- Decimal::signum --SKIPIF-- --FILE-- signum()); var_dump(decimal("-0")->signum()); var_dump(decimal("+0")->signum()); var_dump(decimal("1234.5678E+9")->signum()); var_dump(decimal("1234.5678E-9")->signum()); var_dump(decimal("-1234.5678E+9")->signum()); var_dump(decimal("-1234.5678E-9")->signum()); var_dump(decimal( "INF")->signum()); var_dump(decimal("-INF")->signum()); try { decimal("NAN")->signum(); } catch (RuntimeException $e) { printf("%s\n", $e->getMessage()); } ?> --EXPECT-- int(0) int(0) int(0) int(1) int(1) int(-1) int(-1) int(1) int(-1) Sign of NaN is not defined decimal-1.5.0/tests/php7/methods/sqrt.phpt0000644000000000000000000000276414552656040017202 0ustar rootroot--TEST-- Decimal::sqrt --SKIPIF-- --FILE-- sqrt(); if ((string) $result !== (string) $expect || $result->precision() !== $precision) { print_r(compact("number", "result", "expect", "precision")); } } /* Test that ceil does not modify the original */ $number = decimal("1.5"); $result = $number->sqrt(); if ((string) $number !== "1.5") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/sub.phpt0000644000000000000000000000616214552656040016776 0ustar rootroot--TEST-- Decimal::sub --SKIPIF-- --FILE-- $test) { list($op1, $op2, $expect, $precision) = $test; $results = [ $op1->sub($op2), $op1 - $op2, ]; foreach ($results as $result) { if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "op1", "op2", "result", "expect", "precision")); break; } } } /* Test that op does not modify the original */ $number = new Decimal("2"); $number->sub(5); $number - 5; if ((string) $number !== "2") { var_dump("Mutated!", compact("number")); } ?> --EXPECTF-- Warning: Loss of data on string conversion in %s on line 14 Warning: Loss of data on string conversion in %s on line 15 Warning: Loss of data on string conversion in %s on line 39 Warning: Loss of data on string conversion in %s on line 40 Warning: Loss of data on string conversion in %s on line 44 Warning: Loss of data on string conversion in %s on line 45 decimal-1.5.0/tests/php7/methods/sum.phpt0000644000000000000000000000410014552656040016777 0ustar rootroot--TEST-- Decimal::sum --SKIPIF-- = 80000) echo "skip"; ?> --FILE-- $test) { list($args, $expect, $precision) = $test; $result = Decimal::sum(...$args); if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "args", "result", "expect", "precision")); } } /* Test non-traversable */ try { Decimal::sum("abc"); } catch (TypeError $e) { printf("%s\n", $e->getMessage()); } /* Test non-integer precision */ try { Decimal::sum([], 'a'); } catch (TypeError $e) { printf("%s\n", $e->getMessage()); } /* Test bad types */ try { Decimal::sum([1, 2, "abc"]); } catch (DomainException $e) { printf("%s\n", $e->getMessage()); } ?> --EXPECTF-- Decimal\Decimal::sum() expected parameter 1 to be an array or traversable object, string given Argument 2 passed to Decimal\Decimal::sum() must be of the type int%s string given Failed to parse string as decimal: "abc" decimal-1.5.0/tests/php7/methods/toFixed.phpt0000644000000000000000000000437414552656040017612 0ustar rootroot--TEST-- Decimal::toFixed --SKIPIF-- --FILE-- toFixed($places, $grouped, $rounding); if ($result !== $expect) { print_r(compact("number", "places", "rounding", "result", "expect")); } } /** * Test default rounding. */ var_dump((new Decimal("1.125"))->toFixed(2)); /** * Test default comma separation. */ var_dump((new Decimal("1125"))->toFixed(2, true)); var_dump((new Decimal("1125"))->toFixed(2)); /** * Test default places. */ var_dump((new Decimal("1.125"))->toFixed()); /** * Test negative places. */ var_dump((new Decimal("1.125"))->toFixed(-1)); ?> --EXPECTF-- string(4) "1.12" string(8) "1,125.00" string(7) "1125.00" string(1) "1" Warning: Uncaught InvalidArgumentException: The number of decimal places must be non-negative in %s:%d Stack trace: #0 %s(%d): Decimal\Decimal->toFixed(-1) #1 {main} thrown in %s on line %d Fatal error: Unexpected error in %s on line %d decimal-1.5.0/tests/php7/methods/toFloat.phpt0000644000000000000000000000246314552656040017615 0ustar rootroot--TEST-- Decimal::toFloat --SKIPIF-- --FILE-- toFloat(); } catch (Throwable $e) { printf("%s: %s\n", get_class($e), $e->getMessage()); continue; } if ($result !== $expect) { print_r(compact("number", "result", "expect")); } } /* Test that toint does not modify the original */ $number = decimal("2.5"); $result = $number->toFloat(); if ((string) $number !== "2.5") { var_dump("Mutated!", compact("number")); } /* Test NAN */ var_dump(decimal("NAN")->toFloat()); ?> --EXPECT-- UnderflowException: Floating point underflow float(NAN) decimal-1.5.0/tests/php7/methods/toInt.phpt0000644000000000000000000000337714552656040017307 0ustar rootroot--TEST-- Decimal::toInt --SKIPIF-- --FILE-- toInt(); } catch (Throwable $e) { printf("%s: %s\n", get_class($e), $e->getMessage()); continue; } if ($result !== $expect) { print_r(compact("number", "result", "expect")); } } /* Test that toint does not modify the original */ $number = decimal("2.5"); $result = $number->toInt(); if ((string) $number !== "2.5") { var_dump("Mutated!", compact("number")); } ?> --EXPECT-- RuntimeException: Converting NaN or Inf to integer is not defined RuntimeException: Converting NaN or Inf to integer is not defined RuntimeException: Converting NaN or Inf to integer is not defined OverflowException: Integer overflow TypeError: Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, float given TypeError: Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, float given decimal-1.5.0/tests/php7/methods/toString.phpt0000644000000000000000000000520014552656040020006 0ustar rootroot--TEST-- Decimal::toString --SKIPIF-- --FILE-- toString(), decimal($number)->__toString(), ]; foreach ($results as $result) { if ($result !== $expect) { print_r(compact("number", "result", "expect")); break; } } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/trim.phpt0000644000000000000000000000121114552656040017146 0ustar rootroot--TEST-- Decimal::trim --SKIPIF-- --FILE-- trim(); if ((string) $result !== $expect) { print_r(compact("number", "result", "expect")); } } ?> --EXPECT-- decimal-1.5.0/tests/php7/methods/truncate.phpt0000644000000000000000000000150314552656040020024 0ustar rootroot--TEST-- Decimal::truncate --SKIPIF-- --FILE-- truncate()); var_dump((string) decimal("1234.5678")->truncate()); var_dump((string) decimal("1E-500")->truncate()); var_dump((string) decimal( "NAN")->truncate()); var_dump((string) decimal( "INF")->truncate()); var_dump((string) decimal("-INF")->truncate()); /** * Check that truncate does not modify the original */ $obj = decimal("1.234"); $obj->truncate(); var_dump((string) $obj); ?> --EXPECT-- string(1) "0" string(4) "1234" string(1) "0" string(3) "NAN" string(3) "INF" string(4) "-INF" string(5) "1.234" decimal-1.5.0/tests/php7/cast.phpt0000644000000000000000000000607314552656040015475 0ustar rootroot--TEST-- Decimal cast to string, bool, int, float --SKIPIF-- --FILE-- getMessage()); } try { (float) decimal("1E-1000"); } catch (UnderflowException $e) { printf("B %s\n", $e->getMessage()); } try { (float) decimal("-1E-1000"); } catch (UnderflowException $e) { printf("C %s\n", $e->getMessage()); } try { (int) decimal(NAN); } catch (RuntimeException $e) { printf("D %s\n", $e->getMessage()); } try { (int) decimal(INF); } catch (RuntimeException $e) { printf("E %s\n", $e->getMessage()); } try { (int) decimal(-INF); } catch (RuntimeException $e) { printf("F %s\n", $e->getMessage()); } ?> --EXPECT-- A Integer overflow B Floating point underflow C Floating point underflow D Converting NaN or Inf to integer is not defined E Converting NaN or Inf to integer is not defined F Converting NaN or Inf to integer is not defined decimal-1.5.0/tests/php7/clone.phpt0000644000000000000000000000063014552656040015634 0ustar rootroot--TEST-- Decimal clone --SKIPIF-- --FILE-- --EXPECT-- object(Decimal\Decimal)#1 (2) { ["value"]=> string(5) "1.234" ["precision"]=> int(16) } object(Decimal\Decimal)#2 (2) { ["value"]=> string(5) "1.234" ["precision"]=> int(16) } decimal-1.5.0/tests/php7/constants.phpt0000644000000000000000000000121614552656040016551 0ustar rootroot--TEST-- Decimal constants --SKIPIF-- --FILE-- --EXPECTF-- int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) int(%d) decimal-1.5.0/tests/php7/empty.phpt0000644000000000000000000000102714552656040015673 0ustar rootroot--TEST-- Decimal empty --SKIPIF-- --FILE-- --EXPECT-- bool(false) bool(false) bool(false) bool(false) bool(false) bool(false) bool(false) bool(false) decimal-1.5.0/tests/php7/json.phpt0000644000000000000000000000052014552656040015503 0ustar rootroot--TEST-- Decimal json_encode --SKIPIF-- --FILE-- --EXPECT-- string(8) ""1.2345"" string(8) ""5.0000"" decimal-1.5.0/tests/php7/opcache.phpt0000644000000000000000000000151314552656040016137 0ustar rootroot--TEST-- Disable OPcache pass 2 --SKIPIF-- --INI-- opcache.enable=1 opcache.enable_cli=1 opcache.optimization_level=2 --FILE-- --EXPECT-- object(Decimal\Decimal)#1 (2) { ["value"]=> string(35) "0.000004263976243931729575375414851" ["precision"]=> int(28) } decimal-1.5.0/tests/php7/operators.phpt0000644000000000000000000000132214552656040016551 0ustar rootroot--TEST-- Decimal operators --SKIPIF-- --FILE-- --EXPECT-- object(Decimal\Decimal)#2 (2) { ["value"]=> string(4) "2E+8" ["precision"]=> int(12) } object(Decimal\Decimal)#1 (2) { ["value"]=> string(4) "1E+8" ["precision"]=> int(12) } int(0) decimal-1.5.0/tests/php7/properties.phpt0000644000000000000000000000125114552656040016730 0ustar rootroot--TEST-- Decimal object properties --SKIPIF-- --FILE-- test = 5; unset($decimal->test); isset($decimal->test); var_dump($decimal->test); try { $decimal[0] = 5; } catch (Error $e) { printf("%s\n", $e->getMessage()); } echo "Done"; ?> --EXPECTF-- Notice: Object properties are not supported in %s on line 6 Notice: Object properties are not supported in %s on line 7 Notice: Object properties are not supported in %s on line 8 Notice: Object properties are not supported in %s on line 9 NULL Cannot use object of type Decimal\Decimal as array Done decimal-1.5.0/tests/php7/reflection.phpt0000644000000000000000000002557114552656040016701 0ustar rootroot--TEST-- Decimal object reflection --SKIPIF-- = 80000) echo "skip"; ?> --FILE-- getConstants()); /** * Properties */ var_dump($class->getProperties()); /** * Method summary. */ function print_method_summary(ReflectionClass $class, string $method) { $method = $class->getMethod($method); printf("%s\n", $method->getName()); printf("Number of parameters: %d\n", $method->getNumberOfParameters()); printf("Number of required parameters: %d\n", $method->getNumberOfRequiredParameters()); printf("Return type: %s\n", $method->hasReturnType() ? (PHP_VERSION_ID >= 70100 ? $method->getReturnType()->getName() : (string) $method->getReturnType()) : "void"); printf("Allows return null? %s\n", $method->hasReturnType() ? ($method->getReturnType()->allowsNull() ? "yes" : "no") : "no"); printf("Returns reference? %s\n", $method->returnsReference() ? "yes" : "no"); printf("Parameters:\n"); foreach ($method->getParameters() as $parameter) { printf("Name: %s\n", $parameter->getName()); printf("Type: %s\n", ($parameter->hasType() ? (PHP_VERSION_ID >= 70100 ? $parameter->getType()->getName() : (string) $parameter->getType()) : "mixed")); printf("Reference? %s\n", $parameter->isPassedByReference() ? "yes" : "no"); printf("Allows null? %s\n", $parameter->allowsNull() ? "yes" : "no"); printf("Optional? %s\n", $parameter->isOptional() ? "yes" : "no"); } printf("\n"); } print_method_summary($class, "__construct"); print_method_summary($class, "copy"); print_method_summary($class, "add"); print_method_summary($class, "sub"); print_method_summary($class, "mul"); print_method_summary($class, "div"); print_method_summary($class, "rem"); print_method_summary($class, "mod"); print_method_summary($class, "pow"); print_method_summary($class, "ln"); print_method_summary($class, "exp"); print_method_summary($class, "log10"); print_method_summary($class, "sqrt"); print_method_summary($class, "floor"); print_method_summary($class, "ceil"); print_method_summary($class, "truncate"); print_method_summary($class, "round"); print_method_summary($class, "shift"); print_method_summary($class, "precision"); print_method_summary($class, "signum"); print_method_summary($class, "parity"); print_method_summary($class, "abs"); print_method_summary($class, "negate"); print_method_summary($class, "isEven"); print_method_summary($class, "isOdd"); print_method_summary($class, "isPositive"); print_method_summary($class, "isNegative"); print_method_summary($class, "isNaN"); print_method_summary($class, "isInf"); print_method_summary($class, "isInteger"); print_method_summary($class, "isZero"); print_method_summary($class, "toFixed"); print_method_summary($class, "toString"); print_method_summary($class, "toInt"); print_method_summary($class, "toFloat"); print_method_summary($class, "equals"); print_method_summary($class, "compareTo"); print_method_summary($class, "sum"); print_method_summary($class, "avg"); print_method_summary($class, "__toString"); print_method_summary($class, "jsonSerialize"); ?> --EXPECTF-- array(13) { ["ROUND_UP"]=> int(101) ["ROUND_DOWN"]=> int(102) ["ROUND_CEILING"]=> int(103) ["ROUND_FLOOR"]=> int(104) ["ROUND_HALF_UP"]=> int(105) ["ROUND_HALF_DOWN"]=> int(106) ["ROUND_HALF_EVEN"]=> int(107) ["ROUND_HALF_ODD"]=> int(108) ["ROUND_TRUNCATE"]=> int(109) ["DEFAULT_PRECISION"]=> int(28) ["DEFAULT_ROUNDING"]=> int(107) ["MIN_PRECISION"]=> int(1) ["MAX_PRECISION"]=> int(%d) } array(0) { } __construct Number of parameters: 2 Number of required parameters: 0 Return type: void Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? yes Name: precision Type: int Reference? no Allows null? no Optional? yes copy Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: add Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no sub Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no mul Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no div Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no rem Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no mod Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no pow Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? no Optional? no ln Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: exp Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: log10 Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: sqrt Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: floor Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: ceil Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: truncate Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: round Number of parameters: 2 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? yes Optional? yes Name: rounding Type: int Reference? no Allows null? yes Optional? yes shift Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? no Optional? no precision Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: signum Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: parity Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: abs Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: negate Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: isEven Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isOdd Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isPositive Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isNegative Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isNaN Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isInf Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isInteger Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isZero Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: toFixed Number of parameters: 3 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? yes Optional? yes Name: commas Type: bool Reference? no Allows null? yes Optional? yes Name: rounding Type: int Reference? no Allows null? yes Optional? yes toString Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: toInt Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: toFloat Number of parameters: 0 Number of required parameters: 0 Return type: float Allows return null? no Returns reference? no Parameters: equals Number of parameters: 1 Number of required parameters: 1 Return type: bool Allows return null? no Returns reference? no Parameters: Name: other Type: mixed Reference? no Allows null? no Optional? no compareTo Number of parameters: 1 Number of required parameters: 1 Return type: int Allows return null? no Returns reference? no Parameters: Name: other Type: mixed Reference? no Allows null? no Optional? no sum Number of parameters: 2 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: values Type: mixed Reference? no Allows null? no Optional? no Name: precision Type: int Reference? no Allows null? yes Optional? yes avg Number of parameters: 2 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: values Type: mixed Reference? no Allows null? no Optional? no Name: precision Type: int Reference? no Allows null? yes Optional? yes __toString Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: jsonSerialize Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: decimal-1.5.0/tests/php7/serialize.phpt0000644000000000000000000000070214552656040016523 0ustar rootroot--TEST-- Decimal serialize/unserialize --SKIPIF-- --FILE-- --EXPECT-- string(54) "C:15:"Decimal\Decimal":26:{s:13:"1.2345678E+12";i:42;}" object(Decimal\Decimal)#2 (2) { ["value"]=> string(13) "1.2345678E+12" ["precision"]=> int(42) } decimal-1.5.0/tests/php8/methods/__construct.phpt0000644000000000000000000000770514552656040020534 0ustar rootroot--TEST-- Decimal::__construct --SKIPIF-- = 80100) echo "skip"; ?> --FILE-- $test) { list($args, $precision, $expect) = $test; $result = new Decimal(...$args); if ($result->precision() !== $precision || (string) $result !== $expect) { var_dump(compact("index", "args", "result", "precision", "expect")); } } try { new Decimal(" 1"); } catch (DomainException $e) { printf("A %s\n", $e->getMessage()); } try { new Decimal("1 "); } catch (DomainException $e) { printf("B %s\n", $e->getMessage()); } try { new Decimal(1.5); } catch (TypeError $e) { printf("C %s\n", $e->getMessage()); } try { new Decimal(null); } catch (TypeError $e) { printf("D %s\n", $e->getMessage()); } try { new Decimal(0, "b"); } catch (TypeError $e) { printf("E %s\n", $e->getMessage()); } try { new Decimal(0, null); } catch (OutOfRangeException $e) { printf("F %s\n", $e->getMessage()); } try { new Decimal(0, 0); } catch (OutOfRangeException $e) { printf("G %s\n", $e->getMessage()); } try { new Decimal(0, -1); } catch (OutOfRangeException $e) { printf("H %s\n", $e->getMessage()); } /* Check max precision */ try { new Decimal(0, Decimal::MAX_PRECISION + 1); } catch (OutOfRangeException $e) { printf("I %s\n", $e->getMessage()); } try { (new Decimal())->__construct(); } catch (BadMethodCallException $e) { printf("J %s\n", $e->getMessage()); } ?> --EXPECTF-- Warning: Loss of data on integer conversion in %s on line %d Warning: Loss of data on string conversion in %s on line %d A Failed to parse string as decimal: " 1" B Failed to parse string as decimal: "1 " C Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, float given D Decimal\Decimal::__construct() expected parameter 1 to be a string, integer, or decimal, null given E Decimal\Decimal::__construct(): Argument #2 ($precision) must be of type int, string given F Decimal precision out of range G Decimal precision out of range H Decimal precision out of range I Decimal precision out of range J Decimal objects are immutabledecimal-1.5.0/tests/php8/methods/sum.phpt0000644000000000000000000000407714552656040017015 0ustar rootroot--TEST-- Decimal::sum --SKIPIF-- --FILE-- $test) { list($args, $expect, $precision) = $test; $result = Decimal::sum(...$args); if ([(string) $result, $result->precision()] !== [$expect, $precision]) { print_r(compact("index", "args", "result", "expect", "precision")); } } /* Test non-traversable */ try { Decimal::sum("abc"); } catch (TypeError $e) { printf("%s\n", $e->getMessage()); } /* Test non-integer precision */ try { Decimal::sum([], 'a'); } catch (TypeError $e) { printf("%s\n", $e->getMessage()); } /* Test bad types */ try { Decimal::sum([1, 2, "abc"]); } catch (DomainException $e) { printf("%s\n", $e->getMessage()); } ?> --EXPECTF-- Decimal\Decimal::sum() expected parameter 1 to be an array or traversable object, string given Decimal\Decimal::sum(): Argument #2 ($precision) must be of type int, string given Failed to parse string as decimal: "abc" decimal-1.5.0/tests/php8/reflection.phpt0000644000000000000000000002560414552656040016677 0ustar rootroot--TEST-- Decimal object reflection --SKIPIF-- --FILE-- getConstants()); /** * Properties */ var_dump($class->getProperties()); /** * Method summary. */ function print_method_summary(ReflectionClass $class, string $method) { $method = $class->getMethod($method); printf("%s\n", $method->getName()); printf("Number of parameters: %d\n", $method->getNumberOfParameters()); printf("Number of required parameters: %d\n", $method->getNumberOfRequiredParameters()); printf("Return type: %s\n", $method->hasReturnType() ? (PHP_VERSION_ID >= 70100 ? $method->getReturnType()->getName() : (string) $method->getReturnType()) : "void"); printf("Allows return null? %s\n", $method->hasReturnType() ? ($method->getReturnType()->allowsNull() ? "yes" : "no") : "no"); printf("Returns reference? %s\n", $method->returnsReference() ? "yes" : "no"); printf("Parameters:\n"); foreach ($method->getParameters() as $parameter) { printf("Name: %s\n", $parameter->getName()); printf("Type: %s\n", ($parameter->hasType() ? (PHP_VERSION_ID >= 70100 ? $parameter->getType()->getName() : (string) $parameter->getType()) : "mixed")); printf("Reference? %s\n", $parameter->isPassedByReference() ? "yes" : "no"); printf("Allows null? %s\n", $parameter->allowsNull() ? "yes" : "no"); printf("Optional? %s\n", $parameter->isOptional() ? "yes" : "no"); } printf("\n"); } print_method_summary($class, "__construct"); print_method_summary($class, "copy"); print_method_summary($class, "add"); print_method_summary($class, "sub"); print_method_summary($class, "mul"); print_method_summary($class, "div"); print_method_summary($class, "rem"); print_method_summary($class, "mod"); print_method_summary($class, "pow"); print_method_summary($class, "ln"); print_method_summary($class, "exp"); print_method_summary($class, "log10"); print_method_summary($class, "sqrt"); print_method_summary($class, "floor"); print_method_summary($class, "ceil"); print_method_summary($class, "truncate"); print_method_summary($class, "round"); print_method_summary($class, "shift"); print_method_summary($class, "precision"); print_method_summary($class, "signum"); print_method_summary($class, "parity"); print_method_summary($class, "abs"); print_method_summary($class, "negate"); print_method_summary($class, "isEven"); print_method_summary($class, "isOdd"); print_method_summary($class, "isPositive"); print_method_summary($class, "isNegative"); print_method_summary($class, "isNaN"); print_method_summary($class, "isInf"); print_method_summary($class, "isInteger"); print_method_summary($class, "isZero"); print_method_summary($class, "toFixed"); print_method_summary($class, "toString"); print_method_summary($class, "toInt"); print_method_summary($class, "toFloat"); print_method_summary($class, "equals"); print_method_summary($class, "compareTo"); print_method_summary($class, "sum"); print_method_summary($class, "avg"); print_method_summary($class, "__toString"); print_method_summary($class, "jsonSerialize"); ?> --EXPECTF-- array(13) { ["ROUND_UP"]=> int(101) ["ROUND_DOWN"]=> int(102) ["ROUND_CEILING"]=> int(103) ["ROUND_FLOOR"]=> int(104) ["ROUND_HALF_UP"]=> int(105) ["ROUND_HALF_DOWN"]=> int(106) ["ROUND_HALF_EVEN"]=> int(107) ["ROUND_HALF_ODD"]=> int(108) ["ROUND_TRUNCATE"]=> int(109) ["DEFAULT_PRECISION"]=> int(28) ["DEFAULT_ROUNDING"]=> int(107) ["MIN_PRECISION"]=> int(1) ["MAX_PRECISION"]=> int(%d) } array(0) { } __construct Number of parameters: 2 Number of required parameters: 0 Return type: void Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? yes Name: precision Type: int Reference? no Allows null? no Optional? yes copy Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: add Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no sub Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no mul Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no div Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no rem Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no mod Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no pow Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: value Type: mixed Reference? no Allows null? yes Optional? no ln Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: exp Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: log10 Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: sqrt Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: floor Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: ceil Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: truncate Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: round Number of parameters: 2 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? yes Optional? yes Name: rounding Type: int Reference? no Allows null? yes Optional? yes shift Number of parameters: 1 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? no Optional? no precision Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: signum Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: parity Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: abs Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: negate Number of parameters: 0 Number of required parameters: 0 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: isEven Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isOdd Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isPositive Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isNegative Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isNaN Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isInf Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isInteger Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: isZero Number of parameters: 0 Number of required parameters: 0 Return type: bool Allows return null? no Returns reference? no Parameters: toFixed Number of parameters: 3 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: Name: places Type: int Reference? no Allows null? yes Optional? yes Name: commas Type: bool Reference? no Allows null? yes Optional? yes Name: rounding Type: int Reference? no Allows null? yes Optional? yes toString Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: toInt Number of parameters: 0 Number of required parameters: 0 Return type: int Allows return null? no Returns reference? no Parameters: toFloat Number of parameters: 0 Number of required parameters: 0 Return type: float Allows return null? no Returns reference? no Parameters: equals Number of parameters: 1 Number of required parameters: 1 Return type: bool Allows return null? no Returns reference? no Parameters: Name: other Type: mixed Reference? no Allows null? yes Optional? no compareTo Number of parameters: 1 Number of required parameters: 1 Return type: int Allows return null? no Returns reference? no Parameters: Name: other Type: mixed Reference? no Allows null? yes Optional? no sum Number of parameters: 2 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: values Type: mixed Reference? no Allows null? yes Optional? no Name: precision Type: int Reference? no Allows null? yes Optional? yes avg Number of parameters: 2 Number of required parameters: 1 Return type: Decimal\Decimal Allows return null? no Returns reference? no Parameters: Name: values Type: mixed Reference? no Allows null? yes Optional? no Name: precision Type: int Reference? no Allows null? yes Optional? yes __toString Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: jsonSerialize Number of parameters: 0 Number of required parameters: 0 Return type: string Allows return null? no Returns reference? no Parameters: decimal-1.5.0/LICENSE0000644000000000000000000000207214552656040012626 0ustar rootrootThe MIT License (MIT) Copyright (c) 2018 Rudi Theunissen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. decimal-1.5.0/CHANGELOG.md0000644000000000000000000000231114552656040013426 0ustar rootroot# Change Log All notable changes to this project will be documented in this file. This project follows [Semantic Versioning](http://semver.org/). ## [1.5.0] - 2023-01-19 - Fix object handlers to support PHP 8.3 (thank you @Majkl578) ## [1.4.0] - 2021-02-16 - Added support for PHP 8 (thank you @zlodes) - Added support for mpdecimal 2.5+ #48 - Fixed internal exception when passing negative decimal places to toFixed. #52 ## [1.3.0] - 2019-02-11 - Fixed sqrt of -INF returning -INF rather than NAN. #13 - Disable opcache pass 2 due to numeric string to float casts. Thanks @krakjoe ## [1.2.0] - 2019-02-09 - Change signum of NAN returning 1, now throws RuntimeException. #10 - Change toInt of NAN and INF returning 0, now throws RuntimeException. #11 - Fix pthreads incompatibility (properly). #12 ## [1.1.2] - 2019-02-07 - Fix pthreads incompatibility. #12 ## [1.1.1] - 2019-01-24 - Fix `signum` returning 1 for zero, instead of 0. #9 ## [1.1.0] - 2018-11-20 - Added `trim` method to Decimal to trim trailing zeroes. ## [1.0.1] - 2018-10-29 - Add docs and tests to package.xml @remicollet - Smarter libmpdec lib check in config.m4 @remicollet ## [1.0.0] - 2018-10-28 - Initial public release, request for comments. decimal-1.5.0/README.md0000644000000000000000000000145714552656040013106 0ustar rootroot# PHP Decimal Extension [![PECL](https://img.shields.io/badge/PECL-1.5.0-blue.svg)](https://pecl.php.net/package/decimal) Correctly-rounded, arbitrary-precision decimal arithmetic for PHP ## Documentation See https://php-decimal.github.io/ ## Dependencies - PHP 7 or 8 - [mpdecimal](http://www.bytereef.org/mpdecimal/download.html) ## Install ``` pecl install decimal ``` Windows users can find *.dll* files under [releases](https://github.com/php-decimal/ext-decimal/releases). ## Enable You can do this temporarily using `php -dextension=decimal.so` or by adding `extension=decimal.so` to your INI. If you manage PHP with [phpbrew](https://github.com/phpbrew/phpbrew), you can use `phpbrew ext enable decimal`. ## Local development Docker: `./develop.sh` ## Tests ``` php run-tests.php -P -q ``` decimal-1.5.0/config.m40000644000000000000000000000430414552656040013330 0ustar rootrootPHP_ARG_ENABLE(decimal, whether to enable decimal support, [ --enable-decimal Enable decimal support]) PHP_ARG_WITH(libmpdec-path, for libmpdec custom path, [ --with-libmpdec-path libmpdec path], no, no) if test "$PHP_DECIMAL" != "no"; then MACHINE_INCLUDES=$($CC -dumpmachine) AC_MSG_CHECKING([for libmpdec library in default path]) for i in $PHP_LIBMPDEC_PATH /usr /usr/local; do if test -r $i/$PHP_LIBDIR/libmpdec.$SHLIB_SUFFIX_NAME || test -r $i/$PHP_LIBDIR/libmpdec.a; then LIBMPDEC_DIR=$i/$PHP_LIBDIR LIBMPDEC_INC=$i/include AC_MSG_RESULT(found in $LIBMPDEC_DIR) break elif test -r $i/lib64/libmpdec.$SHLIB_SUFFIX_NAME || test -r $i/lib64/libmpdec.a; then LIBMPDEC_DIR=$i/lib64 LIBMPDEC_INC=$i/include AC_MSG_RESULT(found in $LIBMPDEC_DIR) break elif test -r $i/lib/libmpdec.$SHLIB_SUFFIX_NAME || test -r $i/lib/libmpdec.a; then LIBMPDEC_DIR=$i/lib LIBMPDEC_INC=$i/include AC_MSG_RESULT(found in $LIBMPDEC_DIR) break elif test -r $i/lib/$MACHINE_INCLUDES/libmpdec.so ; then LIBMPDEC_DIR=$i/lib/$MACHINE_INCLUDES LIBMPDEC_INC=$i/include/$MACHINE_INCLUDES AC_MSG_RESULT(found in $LIBMPDEC_DIR) break fi done if test -z "$LIBMPDEC_DIR"; then AC_MSG_RESULT([Could not find libmpdec]) AC_MSG_ERROR([Please reinstall libmpdec]) else AC_MSG_CHECKING([for libmpdec headers in default path]) if test -r $LIBMPDEC_INC/mpdecimal.h; then PHP_ADD_INCLUDE($LIBMPDEC_INC) AC_MSG_RESULT(found in $LIBMPDEC_INC) else AC_MSG_WARN(not found) fi fi PHP_CHECK_LIBRARY(mpdec, mpd_version, [ PHP_ADD_LIBRARY_WITH_PATH(mpdec, $LIBMPDEC_DIR, DECIMAL_SHARED_LIBADD) AC_DEFINE(HAVE_LIBMPDEC, 1, [ ]) ],[ AC_MSG_ERROR([Please check your version of libmpdec (2.4+)]) ],[ -L$LIBMPDEC_DIR -lm ]) PHP_NEW_EXTENSION(decimal, php_decimal.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_ADD_EXTENSION_DEP(decimal, standard) PHP_ADD_EXTENSION_DEP(decimal, spl) PHP_ADD_EXTENSION_DEP(decimal, json) PHP_SUBST(DECIMAL_SHARED_LIBADD) fi decimal-1.5.0/config.w320000644000000000000000000000232214552656040013421 0ustar rootroot// $Id$ // vim:ft=javascript var DECIMAL_EXT_NAME ="decimal"; var DECIMAL_EXT_API ="php_decimal.c"; var DECIMAL_EXT_DEP_HEADER ="mpdecimal.h"; var DECIMAL_EXT_FLAGS ="/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"; var DECIMAL_EXT_DEP_LIB_SHARED ="libmpdec.lib"; var DECIMAL_EXT_DEP_LIB_STATIC ="libmpdec_a.lib"; /* --------------------------------------------------------------------- */ ARG_WITH("decimal", "for decimal support", "yes"); if (PHP_DECIMAL == "yes") { var setup_ok = false; var libmpdec_shared = false; if (CHECK_HEADER_ADD_INCLUDE(DECIMAL_EXT_DEP_HEADER, "CFLAGS_DECIMAL")) { if (PHP_DECIMAL_SHARED && CHECK_LIB(DECIMAL_EXT_DEP_LIB_SHARED, DECIMAL_EXT_NAME, PHP_DECIMAL)) { setup_ok = true; libmpdec_shared = true; } else if (CHECK_LIB(DECIMAL_EXT_DEP_LIB_STATIC, DECIMAL_EXT_NAME, PHP_DECIMAL)) { setup_ok = true; } } if (setup_ok) { EXTENSION(DECIMAL_EXT_NAME, DECIMAL_EXT_API, PHP_DECIMAL_SHARED, DECIMAL_EXT_FLAGS); if (libmpdec_shared) { ADD_FLAG("CFLAGS_DECIMAL", "/D USE_DLL=1"); } } else { WARNING("decimal not enabled; libraries and headers not found"); } } decimal-1.5.0/php_decimal.c0000644000000000000000000023444514552656040014245 0ustar rootroot/** * The MIT License (MIT) * Copyright (c) 2018 Rudi Theunissen * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php_decimal.h" /** * Class entry. */ zend_class_entry *php_decimal_ce; /** * Object handlers. */ zend_object_handlers php_decimal_handlers; /******************************************************************************/ /* ERRORS AND DEBUGGING */ /******************************************************************************/ /** * Prints an mpd to stdout. */ static void php_decimal_print_mpd(mpd_t *mpd) { char *str; mpd_to_sci_size(&str, mpd, 1); php_printf("%s", str); mpd_free(str); } /** * Prints a decimal object to stdout. */ static void php_decimal_print(php_decimal_t *obj) { char *str; mpd_to_sci_size(&str, PHP_DECIMAL_MPD(obj), 1); php_printf("{\n repr: %s\n prec: %ld\n}\n", str, obj->prec); mpd_free(str); } /** * Throws an exception when an invalid data type was given to create a decimal. */ static void php_decimal_wrong_type(zval *arg, int index, const char *expected) { const char *space; const char *cname = get_active_class_name(&space); zend_throw_exception_ex(zend_ce_type_error, 0, "%s%s%s() expected parameter %d to be %s, %s given", cname, space, get_active_function_name(), index, expected, zend_zval_type_name(arg) ); } /** * Called when a decimal is constructed using an unsupported data type. */ static void php_decimal_unsupported_type(zval *arg) { php_decimal_wrong_type(arg, 1, "a string, integer, or decimal"); } /** * Called when an operation on many failed, because the values were not given as * an array or traversable object. */ static void php_decimal_expected_iterable(zval *arg) { php_decimal_wrong_type(arg, 1, "an array or traversable object"); } /** * Throws an exception when an invalid precision was given, eg. -1 */ static void php_decimal_precision_out_of_range(zend_long prec) { zend_throw_exception(spl_ce_OutOfRangeException, "Decimal precision out of range", 0); } /** * Called when a memory allocation error has been detected. */ static void php_decimal_memory_error() { zend_error(E_ERROR, "Failed to allocate memory for decimal"); } /** * Called when an unexpected error has occurred, to prevent undefined behaviour. */ static void php_decimal_unknown_error() { zend_error(E_ERROR, "Unexpected error"); } /** * Called when a rounding operation is attempted with an invalid mode. */ static void php_decimal_unsupported_rounding_mode(php_decimal_rounding_t mode) { zend_throw_exception(spl_ce_InvalidArgumentException, "Unsupported rounding mode", 0); } /** * Called when a string conversion failed. */ static void php_decimal_failed_to_parse_string(zend_string * str) { zend_throw_exception_ex(spl_ce_DomainException, 0, "Failed to parse string as decimal: \"%s\"", ZSTR_VAL(str)); } /** * Called when a string conversion did not use all digits of the input. */ static void php_decimal_loss_of_data_on_string_conversion() { zend_error(E_WARNING, "Loss of data on string conversion"); } /** * Called when a PHP integer conversion did not use all digits of the input. */ static void php_decimal_loss_of_data_on_long_conversion() { zend_error(E_WARNING, "Loss of data on integer conversion"); } /** * Called when a decimal could not be rounded, potentially beyond max prec. */ static void php_decimal_failed_to_round() { zend_throw_exception(spl_ce_RuntimeException, "Failed to round decimal", 0); } /** * Called when an attempt was made to divide a decimal by 0. */ static void php_decimal_division_by_zero_error() { zend_throw_exception(zend_ce_division_by_zero_error, "Division by zero", 0); } /** * Called when a value could not be unserialized. This should never happen unless * someone is trying to do something they're not supposed to. */ static void php_decimal_unserialize_error() { zend_throw_exception(spl_ce_InvalidArgumentException, "Could not unserialize decimal", 0); } /** * Called when an attempt is made to read or write object properties. */ static void php_decimal_object_properties_not_supported() { zend_error(E_NOTICE, "Object properties are not supported"); } /** * Called when a decimal is too large to be converted to double. */ static void php_decimal_floating_point_overflow() { zend_throw_exception(spl_ce_OverflowException, "Floating point overflow", 0); } /** * Called when a decimal is too small to be converted to double, eg. 1E-1000 */ static void php_decimal_floating_point_underflow() { zend_throw_exception(spl_ce_UnderflowException, "Floating point underflow", 0); } /** * Called when a decimal is too large to be converted to int, eg. 1E+1000 */ static void php_decimal_integer_overflow() { zend_throw_exception(spl_ce_OverflowException, "Integer overflow", 0); } /** * Called when NaN or Inf is converted to integer. */ static void php_decimal_integer_from_special_is_not_defined() { zend_throw_exception(spl_ce_RuntimeException, "Converting NaN or Inf to integer is not defined", 0); } /** * Called when attempting to query the signum of NaN. */ static void php_decimal_sign_of_nan_is_not_defined() { zend_throw_exception(spl_ce_RuntimeException, "Sign of NaN is not defined", 0); } /** * Called when __construct is called directly on a decimal object. */ static void php_decimal_constructor_already_called() { zend_throw_exception(spl_ce_BadMethodCallException, "Decimal objects are immutable", 0); } /** * Called when some given number of decimal places was not valid. */ static void php_decimal_negative_decimal_places_not_defined() { zend_throw_exception(spl_ce_InvalidArgumentException, "The number of decimal places must be non-negative", 0); } /** * Called when a trap is triggered in mpdecimal when calling signalling * functions (non-quiet). These methods usually don't have the "q" prefix in * their names and don't require a status argument. The non-signalling functions * should be used whenever we do not expect anything to go wrong or when we want * to handle the status directly. This is basically a catch-all error handler. * * See PHP_DECIMAL_TRAPS */ static void php_decimal_mpd_traphandler(mpd_context_t *ctx) { uint32_t status = mpd_getstatus(ctx); /* Potentially out of memory, which should be caught by the PHP allocator. */ if (status & MPD_Malloc_error) { php_decimal_memory_error(); return; } /* ¯\_(ツ)_/¯ */ php_decimal_unknown_error(); } /******************************************************************************/ /* CONTEXT & PRECISION */ /******************************************************************************/ /** * The global, shared mpd context. */ static zend_always_inline mpd_context_t *php_decimal_context() { return &DECIMAL_G(ctx); } /** * Sets the significand precision of a given decimal object. */ static zend_always_inline void php_decimal_set_precision(php_decimal_t *obj, zend_long prec) { obj->prec = prec; } /** * Returns the significand precision of a given decimal object. */ static zend_always_inline zend_long php_decimal_get_precision(php_decimal_t *obj) { return obj->prec; } /** * Returns true if the given precision is valid, false otherwise. */ static zend_always_inline zend_bool php_decimal_precision_is_valid(zend_long prec) { return prec >= PHP_DECIMAL_MIN_PREC && prec <= PHP_DECIMAL_MAX_PREC; } /** * Checks that a given precision is valid, and returns immediately if it is not. */ #define PHP_DECIMAL_VALID_PRECISION_OR_RETURN(prec) do { \ zend_long p = prec; \ if (php_decimal_precision_is_valid(p) == false) { \ php_decimal_precision_out_of_range(p); \ return; \ } \ } while (0) /******************************************************************************/ /* ALLOC, INIT, FREE, CLONE */ /******************************************************************************/ /** * Resets all flags and allocates the data for a given MPD. We are using this * manual approach to avoid allocating an mpd_t when we allocate a decimal. * * We instead embed the struct itself, rather than a pointer to a new one. * This requires that we borrow some internals from mpdecimal to initialize an * already allocated mpd_t. This would usually be done by mpd_new. */ static void php_decimal_init_mpd(mpd_t *mpd) { mpd->flags = 0; mpd->exp = 0; mpd->digits = 0; mpd->len = 0; mpd->alloc = MPD_MINALLOC; mpd->data = mpd_alloc(MPD_MINALLOC, sizeof(mpd_uint_t)); if (mpd->data == NULL) { php_decimal_memory_error(); } } /** * Initializes an allocated decimal object. */ static void php_decimal_init(php_decimal_t *obj) { php_decimal_init_mpd(PHP_DECIMAL_MPD(obj)); } /** * Allocates a new php_decimal_t that has not been initialized. We don't want to * initialize a decimal until its constructor is called, so that we can check in * the constructor whether the object has already been initialized. */ static php_decimal_t *php_decimal_alloc() { php_decimal_t *obj = ecalloc(1, sizeof(php_decimal_t)); if (obj) { #if PHP_VERSION_ID < 80300 obj->std.handlers = &php_decimal_handlers; #endif zend_object_std_init((zend_object *) obj, php_decimal_ce); } else { php_decimal_memory_error(); } return obj; } /** * Creates a new decimal object, initialized to a given precision. */ static php_decimal_t *php_decimal_with_prec(zend_long prec) { php_decimal_t *obj = php_decimal_alloc(); php_decimal_init(obj); php_decimal_set_precision(obj, prec); return obj; } /** * Creates a new decimal object, initialized to the default precision. */ static zend_always_inline php_decimal_t *php_decimal() { return php_decimal_with_prec(PHP_DECIMAL_DEFAULT_PRECISION); } /** * Creates a custom zend_object that is also an uninitialized decimal object. */ static zend_object *php_decimal_create_object(zend_class_entry *ce) { return (zend_object *) php_decimal_alloc(); } /** * Creates a copy of the given decimal object. */ static php_decimal_t *php_decimal_create_copy(php_decimal_t *src) { php_decimal_t *dst = php_decimal_with_prec(php_decimal_get_precision(src)); mpd_copy(PHP_DECIMAL_MPD(dst), PHP_DECIMAL_MPD(src), php_decimal_context()); return dst; } /** * Clones the given zval/zend_object, which must be a decimal object. */ #if PHP_VERSION_ID >= 80000 static zend_object *php_decimal_clone_obj(zend_object *obj) { return (zend_object *) php_decimal_create_copy(O_DECIMAL_P(obj)); } #else static zend_object *php_decimal_clone_obj(zval *obj) { return (zend_object *) php_decimal_create_copy(Z_DECIMAL_P(obj)); } #endif /** * Frees all internal memory used by a given decimal, but not object itself. */ static void php_decimal_release(php_decimal_t *obj) { /* The mpd_t is embedded so will be freed along with the object. */ if (PHP_DECIMAL_IS_INITIALIZED(obj)) { mpd_free(PHP_DECIMAL_MPD(obj)->data); } zend_object_std_dtor((zend_object *) obj); } /** * Frees all internal memory used by a given decimal. */ static void php_decimal_free(php_decimal_t *obj) { php_decimal_release(obj); efree(obj); } /** * Free handler, should only free internal memory, not the object itself. */ static void php_decimal_free_object(zend_object *obj) { php_decimal_release((php_decimal_t*) obj); } /******************************************************************************/ /* ROUNDING */ /******************************************************************************/ /** * This library supports both its own rounding constants and the PHP rounding * mode constants. The user should not have to wonder which constant to use. * * In order to support both, as well as some modes not supported by mpdecimal, * we need to convert the requested mode to an mpdecimal rounding mode. */ static php_decimal_rounding_t php_decimal_convert_to_mpd_rounding_mode(mpd_t *mpd, zend_long scale, php_decimal_rounding_t mode) { switch (mode) { case PHP_DECIMAL_ROUND_UP: return MPD_ROUND_UP; case PHP_DECIMAL_ROUND_DOWN: return MPD_ROUND_DOWN; case PHP_DECIMAL_ROUND_CEILING: return MPD_ROUND_CEILING; case PHP_DECIMAL_ROUND_FLOOR: return MPD_ROUND_FLOOR; case PHP_DECIMAL_ROUND_HALF_UP: return MPD_ROUND_HALF_UP; case PHP_DECIMAL_ROUND_HALF_DOWN: return MPD_ROUND_HALF_DOWN; case PHP_DECIMAL_ROUND_HALF_EVEN: return MPD_ROUND_HALF_EVEN; case PHP_DECIMAL_ROUND_TRUNCATE: return MPD_ROUND_TRUNC; /* PHP constants */ case PHP_ROUND_HALF_UP: return MPD_ROUND_HALF_UP; case PHP_ROUND_HALF_DOWN: return MPD_ROUND_HALF_DOWN; case PHP_ROUND_HALF_EVEN: return MPD_ROUND_HALF_EVEN; /** * Special case for half-odd. * * This depends on the value's parity because half-odd is not * implemented by mpdecimal. We want to support the PHP constant, so we * need to adjust to a mode that is equivalent to an MPD rounding mode. */ case PHP_ROUND_HALF_ODD: case PHP_DECIMAL_ROUND_HALF_ODD: { /* INF and NAN won't be rounded. */ if (UNEXPECTED(mpd_isspecial(mpd))) { return MPD_ROUND_TRUNC; /** * Determine the integer parity at the point of rounding to determine * which way we should round to get to the nearest odd number. * * For example, 0.12345, rounded to 4 decimal places is on the 4. */ } else { uint32_t status = 0; PHP_DECIMAL_TEMP_MPD(tmp); mpd_qshiftl(&tmp, mpd, scale, &status); mpd_qtrunc(&tmp, &tmp, php_decimal_context(), &status); /* An odd digit should round down towards itself. */ mode = mpd_isodd(&tmp) ? MPD_ROUND_HALF_DOWN : MPD_ROUND_HALF_UP; mpd_del(&tmp); return mode; } } /* Couldn't determine a match, rounding mode is not supported. */ default: php_decimal_unsupported_rounding_mode(mode); return 0; } } /** * Rounds a given mpd to a number of decimal places (scale), using a given * php decimal rounding mode. If the scale is beyond the number of decimal * places, trailing zeroes should be appended, increasing significance. */ static void php_decimal_round_mpd(mpd_t *res, mpd_t *mpd, zend_long scale, php_decimal_rounding_t mode) { uint32_t status = 0; /* No need to round if the scale is beyond the number of decimal digits */ if (-scale <= mpd->exp) { mpd_qcopy(res, mpd, &status); return; } mpd_qsetround(php_decimal_context(), php_decimal_convert_to_mpd_rounding_mode(mpd, scale, mode)); mpd_qrescale(res, mpd, -scale, php_decimal_context(), &status); mpd_qsetround(php_decimal_context(), MPD_ROUND_HALF_EVEN); if (status & MPD_Invalid_operation) { php_decimal_failed_to_round(); } } /** * Trims trailing zeroes in-place. */ static void php_decimal_trim_trailing_zeroes(php_decimal_t *obj) { mpd_reduce(PHP_DECIMAL_MPD(obj), PHP_DECIMAL_MPD(obj), php_decimal_context()); } /******************************************************************************/ /* CONVERSIONS */ /******************************************************************************/ /** * Sets the value of a decimal object to infinity. */ static void php_decimal_set_inf(php_decimal_t *obj, zend_bool positive) { mpd_set_infinity(PHP_DECIMAL_MPD(obj)); mpd_set_sign(PHP_DECIMAL_MPD(obj), positive ? MPD_POS : MPD_NEG); } /** * Sets the value of an mpd to NAN. */ static void php_decimal_mpd_set_nan(mpd_t *mpd) { mpd_set_qnan(mpd); } /** * Sets the value of a decimal object to NAN. */ static void php_decimal_set_nan(php_decimal_t *obj) { php_decimal_mpd_set_nan(PHP_DECIMAL_MPD(obj)); } /** * Parses a string to a given precision. Trailing zeroes are not preserved. */ static php_success_t php_decimal_mpd_set_string(mpd_t *mpd, zend_string *str, zend_long prec, zend_bool quiet) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(prec, { mpd_qset_string(mpd, ZSTR_VAL(str), php_decimal_context(), &status); }); /* Check that the conversion was successful. */ if (status & MPD_Conversion_syntax) { if (!quiet) { php_decimal_failed_to_parse_string(str); } return FAILURE; } /* Check that we haven't lost data, ie. string wasn't parsed completely. */ /* Can check MPD_Rounded too if we care about losing trailing zeroes. */ if (status & MPD_Inexact) { php_decimal_loss_of_data_on_string_conversion(); } return SUCCESS; } /** * Sets an mpd to a given double value. Will only be successful if the double is * a special value, ie INF, -INF and NAN. */ static php_success_t php_decimal_mpd_set_special_double(mpd_t *res, double dval) { if (zend_isinf(dval)) { mpd_set_infinity(res); mpd_set_sign(res, dval > 0 ? MPD_POS : MPD_NEG); return SUCCESS; } if (zend_isnan(dval)) { mpd_set_qnan(res); return SUCCESS; } return FAILURE; } /** * Sets an mpd to a given double value. If the double is not a special value, it * will be cast to string first. This is useful because we don't want to parse * floats when constructing, but we do when comparing. */ static void php_decimal_mpd_set_double(mpd_t *res, double dval) { zend_string *str; zval tmp; ZVAL_DOUBLE(&tmp, dval); str = zval_get_string(&tmp); php_decimal_mpd_set_string(res, str, PHP_DECIMAL_MAX_PREC, false); zend_string_free(str); } /** * Sets an mpd to a given long value. The precision is to determine whether data * was lost, eg. 123 with a precision of 2 would be 120. */ static void php_decimal_mpd_set_long(mpd_t *mpd, zend_long lval, zend_long prec) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(prec, { mpd_qset_ssize(mpd, lval, php_decimal_context(), &status); }); if (status & MPD_Rounded) { php_decimal_loss_of_data_on_long_conversion(); } } /** * Sets the value of a given decimal object to zero. */ static void php_decimal_set_zero(php_decimal_t *obj) { mpd_zerocoeff(PHP_DECIMAL_MPD(obj)); } /** * Attempts to parse a non-object value as a decimal, using a given precision. */ static php_success_t php_decimal_parse_scalar_ex(mpd_t *res, zval *value, zend_long prec, zend_bool quiet) { switch (Z_TYPE_P(value)) { case IS_STRING: return php_decimal_mpd_set_string(res, Z_STR_P(value), prec, quiet); case IS_LONG: php_decimal_mpd_set_long(res, Z_LVAL_P(value), prec); return SUCCESS; /* Should only consider special values here - float is not supported. */ case IS_DOUBLE: { if (php_decimal_mpd_set_special_double(res, Z_DVAL_P(value)) == SUCCESS) { return SUCCESS; } } default: { if (!quiet) { php_decimal_unsupported_type(value); } php_decimal_mpd_set_nan(res); return FAILURE; } } } /** * Attempts to parse a zval with a non-decimal value. */ static php_success_t php_decimal_parse_scalar(mpd_t *mpd, zval *value, zend_long prec) { return php_decimal_parse_scalar_ex(mpd, value, prec, false); } /** * Attempts to parse a zval with a non-decimal value, ignoring exceptions. */ static php_success_t php_decimal_parse_scalar_quiet(mpd_t *mpd, zval *value, zend_long prec) { return php_decimal_parse_scalar_ex(mpd, value, prec, true); } /** * Attempts to parse a mixed value into a decimal object, using the decimal * object's internal mpd and precision. The precision of the result should be * the maximum of its already-set precision and the precision of the parsed * value if that value is also decimal object. Scalar values should assume that * the target precision is already set on the result. */ static php_success_t php_decimal_parse_into(php_decimal_t *obj, zval *value) { /* Check if is non-decimal, attempt to parse as scalar */ if (!Z_IS_DECIMAL_P(value)) { return php_decimal_parse_scalar(PHP_DECIMAL_MPD(obj), value, php_decimal_get_precision(obj)); /* Decimal object, set max precision, copy internal value. */ } else { zend_long prec1 = php_decimal_get_precision(obj); zend_long prec2 = php_decimal_get_precision(Z_DECIMAL_P(value)); php_decimal_set_precision(obj, MAX(prec1, prec2)); mpd_copy(PHP_DECIMAL_MPD(obj), Z_DECIMAL_MPD_P(value), php_decimal_context()); return SUCCESS; } } /** * Converts a decimal object to double. Exceptions are thrown if the value of * the decimal is outside of the range of a double. There is no warning or error * if the value of the decimal can not be represented accurately. */ static double php_decimal_to_double(php_decimal_t *obj) { mpd_t *mpd = PHP_DECIMAL_MPD(obj); if (mpd_iszero(mpd)) { return 0; } else if (mpd_isspecial(mpd)) { if (mpd_isqnan(mpd)) { return php_get_nan(); } /* Infinity */ return mpd_ispositive(mpd) ? +php_get_inf() : -php_get_inf(); } else { /* Convert the decimal to a string first. */ char *str = mpd_to_sci(mpd, 1); /* Attempt to parse the string to double. */ double dval = zend_strtod(str, NULL); /* Check if limits were reached. */ if (zend_isinf(dval)) { php_decimal_floating_point_overflow(); } else if (dval == 0 && !mpd_iszero(mpd)) { php_decimal_floating_point_underflow(); } mpd_free(str); return dval; } } /** * Converts a decimal object to long, aka PHP int. An exception will be thrown * if the decimal is outside of the range of a PHP integer. * * TODO: PHP magically turn ints into floats when they get too big. Is that * something we should consider here also? */ static zend_long php_decimal_to_long(php_decimal_t *obj) { const mpd_t *mpd = PHP_DECIMAL_MPD(obj); uint32_t status = 0; zend_long result = 0; /* PHP converts to zero but that does not make sense and could hide bugs. */ if (UNEXPECTED(mpd_isspecial(mpd))) { php_decimal_integer_from_special_is_not_defined(); return 0; } if (mpd_isinteger(mpd)) { result = mpd_qget_ssize(mpd, &status); /* Truncate to an integer value first, otherwise mpd_qget_ssize fails. */ } else { PHP_DECIMAL_TEMP_MPD(tmp); mpd_qtrunc(&tmp, mpd, php_decimal_context(), &status); result = mpd_qget_ssize(&tmp, &status); mpd_del(&tmp); } /* Check for overflow. */ if (status & MPD_Invalid_operation) { php_decimal_integer_overflow(); return 0; } return result; } /** * Converts an mpd to string, following PHP convensions. */ static zend_string *php_decimal_mpd_to_string(mpd_t *mpd) { char *str; zend_string *res; mpd_ssize_t len; PHP_DECIMAL_CHECK_SPECIAL_STRING_RETURN(mpd); len = mpd_to_sci_size(&str, mpd, 1); res = zend_string_init(str, len, 0); mpd_free(str); return res; } /** * Converts a given php_decimal_t to a zend_string. */ static zend_string *php_decimal_to_string(php_decimal_t *obj) { return php_decimal_mpd_to_string(PHP_DECIMAL_MPD(obj)); } /** * Formats an mpd, used by toFixed. */ static zend_string *php_decimal_format_mpd(mpd_t *mpd, zend_long places, zend_bool commas, php_decimal_rounding_t mode) { char *str; zend_string *res; smart_str fmt = {0}; if (places < 0) { php_decimal_negative_decimal_places_not_defined(); } PHP_DECIMAL_TEMP_MPD(tmp); PHP_DECIMAL_CHECK_SPECIAL_STRING_RETURN(mpd); /* Round first */ php_decimal_round_mpd(&tmp, mpd, places, mode); /* Always show negative sign, but never positive. */ smart_str_appendc(&fmt, '-'); /* Specify whether we want to separate thousands with a comma. */ if (commas) { smart_str_appendc(&fmt, ','); } /* Specify how many decimal places we want. */ smart_str_appendc(&fmt, '.'); smart_str_append_long(&fmt, places); /* Fixed point representation. */ smart_str_appendc(&fmt, 'F'); smart_str_0(&fmt); str = mpd_format(&tmp, ZSTR_VAL(fmt.s), php_decimal_context()); res = zend_string_init(str, strlen(str), 0); smart_str_free(&fmt); mpd_free(str); mpd_del(&tmp); return res; } /** * Formats a decimal object, used by toFixed. */ static zend_string *php_decimal_format(php_decimal_t *obj, zend_long places, zend_bool commas, php_decimal_rounding_t mode) { return php_decimal_format_mpd(PHP_DECIMAL_MPD(obj), places, commas, mode); } /******************************************************************************/ /* OPERATIONS */ /******************************************************************************/ static int php_decimal_signum(const mpd_t *mpd) { if (UNEXPECTED(mpd_isnan(mpd))) { php_decimal_sign_of_nan_is_not_defined(); return 0; } return mpd_iszero(mpd) ? 0 : mpd_arith_sign(mpd); } /** * Sets the result of res to op1 + op2, using the precision of res. * * We are not concerned with invalid operations such as -INF + INF because PHP * quietly uses NAN. The intention is to be as consistent with PHP as possible. */ static void php_decimal_add(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qadd(PHP_DECIMAL_MPD(res), op1, op2, php_decimal_context(), &status); }); } /** * Sets the result of res to op1 - op2, using the precision of res. * * We are not concerned with invalid operations such as -INF - INF because PHP * quietly uses NAN. The intention is to be as consistent with PHP as possible. */ static void php_decimal_sub(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qsub(PHP_DECIMAL_MPD(res), op1, op2, php_decimal_context(), &status); }); } /** * Sets the result of res to op1 * op2, using the precision of res. * * We are not concerned with invalid operations such as INF * NAN because PHP * quietly uses NAN. The intention is to be as consistent with PHP as possible. */ static void php_decimal_mul(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qmul(PHP_DECIMAL_MPD(res), op1, op2, php_decimal_context(), &status); }); } /** * Sets the result of res to op1 / op2, using the precision of res. * * Division by zero will throw an exception, but undefined division such as * INF / NAN will quietly return NAN. */ static void php_decimal_div(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { uint32_t status = 0; if (mpd_iszero(op2)) { php_decimal_division_by_zero_error(); php_decimal_set_inf(res, mpd_ispositive(op1)); return; } PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qdiv(PHP_DECIMAL_MPD(res), op1, op2, php_decimal_context(), &status); }); } /** * Sets res to the decimal remainder after dividing op1 by op2. This should not * be used as the equivalent of % because modulo is an integer operation. * * In order to stay consistent with div, invalid operations should quietly * return NAN, even though PHP throws for all invalid modulo operations. */ static void php_decimal_rem(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { uint32_t status = 0; if (mpd_iszero(op2)) { php_decimal_division_by_zero_error(); php_decimal_set_inf(res, mpd_ispositive(op1)); return; } PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qrem(PHP_DECIMAL_MPD(res), op1, op2, php_decimal_context(), &status); }); } /** * Sets res to the integer remainder after dividing op1 by op2. This is the * equivalent of %, ie. integer mod. * * In order to stay consistent with div, invalid operations should quietly * return NAN, even though PHP throws for all invalid modulo operations. */ static void php_decimal_mod(php_decimal_t *res, mpd_t *op1, mpd_t *op2) { PHP_DECIMAL_TEMP_MPD(tmp1); PHP_DECIMAL_TEMP_MPD(tmp2); /* Truncate op1 if not an integer, use res as temp */ if (!mpd_isinteger(op1) && !mpd_isspecial(op1)) { mpd_trunc(&tmp1, op1, php_decimal_context()); op1 = &tmp1; } /* Truncate op2 if not an integer. */ if (!mpd_isinteger(op2) && !mpd_isspecial(op2)) { mpd_trunc(&tmp2, op2, php_decimal_context()); op2 = &tmp2; } php_decimal_rem(res, op1, op2); mpd_del(&tmp1); mpd_del(&tmp2); } /** * Sets rem to the result of raising base to the power of exp. Anything to the * power of zero should equal 1. */ static void php_decimal_pow(php_decimal_t *res, mpd_t *base, mpd_t *exp) { uint32_t status = 0; if (mpd_iszero(exp)) { php_decimal_mpd_set_long(PHP_DECIMAL_MPD(res), 1, php_decimal_get_precision(res)); return; } PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qpow(PHP_DECIMAL_MPD(res), base, exp, php_decimal_context(), &status); }); } /** * Sets res to the natural log of op1. */ static void php_decimal_ln(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qln(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); }); } /** * Sets res to e to the power of exp. */ static void php_decimal_exp(php_decimal_t *res, mpd_t *exp) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qexp(PHP_DECIMAL_MPD(res), exp, php_decimal_context(), &status); }); } /** * Sets res to the base-10 log of op1. */ static void php_decimal_log10(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qlog10(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); }); } /** * Sets res to the square root of op1. Negative values should quietly return * NAN, and special numbers should be copied as is. */ static void php_decimal_sqrt(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; if (mpd_isnegative(op1)) { php_decimal_set_nan(res); return; } if (mpd_isspecial(op1)) { mpd_qcopy(PHP_DECIMAL_MPD(res), op1, &status); return; } PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qsqrt(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); }); } /** * Sets res to the floor of op1, ie. rounded down towards negative infinity. */ static void php_decimal_floor(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; if (mpd_isspecial(op1)) { mpd_qcopy(PHP_DECIMAL_MPD(res), op1, &status); return; } mpd_qfloor(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); } /** * Sets res to the ceiling of op1, ie. rounded up towards positive infinity. */ static void php_decimal_ceil(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; if (mpd_isspecial(op1)) { mpd_qcopy(PHP_DECIMAL_MPD(res), op1, &status); return; } mpd_qceil(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); } /** * Sets res to the integer value of op1, ie. discard all digits behind the * decimal point. The result is guaranteed to be an integer, unless op1 is a * special number in which case it should be copied as is. */ static void php_decimal_truncate(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; if (mpd_isspecial(op1)) { mpd_qcopy(PHP_DECIMAL_MPD(res), op1, &status); return; } mpd_qtrunc(PHP_DECIMAL_MPD(res), op1, php_decimal_context(), &status); } /** * Sets res to the value of op1 after shifting its decimal point. */ static void php_decimal_shift(php_decimal_t *res, mpd_t *op1, zend_long places) { uint32_t status = 0; PHP_DECIMAL_TEMP_MPD(exp); php_decimal_mpd_set_long(&exp, places, php_decimal_get_precision(res)); PHP_DECIMAL_WITH_PRECISION(php_decimal_get_precision(res), { mpd_qscaleb(PHP_DECIMAL_MPD(res), op1, &exp, php_decimal_context(), &status); }); mpd_del(&exp); } /** * Sets res to the absolute value of op1. */ static void php_decimal_abs(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; mpd_qcopy_abs(PHP_DECIMAL_MPD(res), op1, &status); } /** * Sets res to the value of op1 with its sign inverted. */ static void php_decimal_negate(php_decimal_t *res, mpd_t *op1) { uint32_t status = 0; mpd_qcopy_negate(PHP_DECIMAL_MPD(res), op1, &status); } /** * Sets res to the sum of all values in the given array, returning the number of * values that were counted or -1 if the operation failed. */ static zend_long php_decimal_sum_array(php_decimal_t *res, HashTable *arr) { zval *value; php_decimal_set_zero(res); ZEND_HASH_FOREACH_VAL(arr, value) { mpd_t *op1; mpd_t *op2; zend_long prec; PHP_DECIMAL_TEMP_MPD(tmp); if (Z_IS_DECIMAL_P(value)) { op1 = PHP_DECIMAL_MPD(res); op2 = Z_DECIMAL_MPD_P(value); prec = MAX(php_decimal_get_precision(Z_DECIMAL_P(value)), php_decimal_get_precision(res)); } else { op1 = PHP_DECIMAL_MPD(res); op2 = &tmp; prec = php_decimal_get_precision(res); /* Attempt to parse the value, otherwise bail out. */ if (php_decimal_parse_scalar(&tmp, value, prec) == FAILURE) { mpd_del(&tmp); return -1; } } /* Set precision, do add. */ php_decimal_set_precision(res, prec); php_decimal_add(res, op1, op2); mpd_del(&tmp); } ZEND_HASH_FOREACH_END(); return zend_hash_num_elements(arr); } /** * Sets res to the sum of all values in the given traversable, returning the * number of values that were counted or -1 if the operation failed. */ static zend_long php_decimal_sum_traversable(php_decimal_t *res, zval *values) { zend_long count = 0; php_decimal_set_zero(res); /* Get the iterator from the object. */ zend_object_iterator *iterator = Z_OBJCE_P(values)->get_iterator(Z_OBJCE_P(values), values, 0); if (EG(exception)) { goto done; } /* Attempt to rewind the iterator. */ iterator->index = 0; if (iterator->funcs->rewind) { iterator->funcs->rewind(iterator); if (EG(exception)) { goto done; } } /* While the iterator has a value for us... */ while (iterator->funcs->valid(iterator) == SUCCESS) { mpd_t *op1; mpd_t *op2; zend_long prec; PHP_DECIMAL_TEMP_MPD(tmp); /* Attempt to access the current value of the iterator. */ zval *value = iterator->funcs->get_current_data(iterator); if (EG(exception)) { goto done; } if (Z_IS_DECIMAL_P(value)) { op1 = PHP_DECIMAL_MPD(res); op2 = Z_DECIMAL_MPD_P(value); prec = MAX(php_decimal_get_precision(Z_DECIMAL_P(value)), php_decimal_get_precision(res)); } else { op1 = PHP_DECIMAL_MPD(res); op2 = &tmp; prec = php_decimal_get_precision(res); /* Attempt to parse the value, otherwise bail out. */ if (php_decimal_parse_scalar(&tmp, value, prec) == FAILURE) { mpd_del(&tmp); goto done; } } /* Set precision, do add. */ php_decimal_set_precision(res, prec); php_decimal_add(res, op1, op2); mpd_del(&tmp); /* Update count, move iterator forward. */ count++; iterator->index++; iterator->funcs->move_forward(iterator); if (EG(exception)) { goto done; } } done: if (iterator) { zend_iterator_dtor(iterator); } return EG(exception) ? -1 : count; } /** * Sets res to the sum of all given values, returning the number of values that * were counted, or -1 if the operation failed. */ static zend_long php_decimal_sum(php_decimal_t *res, zval *values) { /* Check array first */ if (Z_TYPE_P(values) == IS_ARRAY) { return php_decimal_sum_array(res, Z_ARR_P(values)); } /* Check traversable object */ if (Z_TYPE_P(values) == IS_OBJECT && instanceof_function(Z_OBJCE_P(values), zend_ce_traversable)) { return php_decimal_sum_traversable(res, values); } php_decimal_expected_iterable(values); return -1; } /** * Sets res to the average of all given values, or NAN if the operation failed. */ static php_success_t php_decimal_avg(php_decimal_t *res, zval *values) { zend_long count = php_decimal_sum(res, values); if (count == 0) { php_decimal_set_zero(res); return SUCCESS; } if (count > 0) { PHP_DECIMAL_TEMP_MPD(tmp); php_decimal_mpd_set_long(&tmp, count, PHP_DECIMAL_MAX_PREC); php_decimal_div(res, PHP_DECIMAL_MPD(res), &tmp); mpd_del(&tmp); return SUCCESS; } php_decimal_set_nan(res); return FAILURE; } /** * Converts a zend opcode to a binary arithmetic function pointer. * * Returns NULL if a function is not mapped. */ static php_decimal_binary_op_t php_decimal_get_operation_for_opcode(zend_uchar opcode) { switch (opcode) { case ZEND_ADD: return php_decimal_add; case ZEND_SUB: return php_decimal_sub; case ZEND_MUL: return php_decimal_mul; case ZEND_DIV: return php_decimal_div; case ZEND_MOD: return php_decimal_mod; case ZEND_POW: return php_decimal_pow; default: return NULL; } } /** * Attempts a binary operation on two zval's, writing the result to res. * * We don't know which of the operands is a decimal, if not both. */ static void php_decimal_do_binary_op(php_decimal_binary_op_t op, php_decimal_t *res, zval *op1, zval *op2) { mpd_t *mpd1; mpd_t *mpd2; zend_long prec; PHP_DECIMAL_TEMP_MPD(tmp); if (Z_IS_DECIMAL_P(op1)) { if (Z_IS_DECIMAL_P(op2)) { /* Both operands are decimal */ mpd1 = Z_DECIMAL_MPD_P(op1); mpd2 = Z_DECIMAL_MPD_P(op2); prec = MAX(php_decimal_get_precision(Z_DECIMAL_P(op1)), php_decimal_get_precision(Z_DECIMAL_P(op2))); } else { /* Only op1 is decimal, so attempt to parse op2. */ mpd1 = Z_DECIMAL_MPD_P(op1); mpd2 = &tmp; prec = php_decimal_get_precision(Z_DECIMAL_P(op1)); /* Failing to parse will throw an exception, so set NAN defer.*/ if (php_decimal_parse_scalar(mpd2, op2, prec) == FAILURE) { php_decimal_set_nan(res); mpd_del(&tmp); return; } } } else { /* op1 is NOT a decimal, so op2 must be. */ mpd1 = &tmp; mpd2 = Z_DECIMAL_MPD_P(op2); prec = php_decimal_get_precision(Z_DECIMAL_P(op2)); /* Failing to parse will throw an exception, so set NAN defer.*/ if (php_decimal_parse_scalar(mpd1, op1, prec) == FAILURE) { php_decimal_set_nan(res); mpd_del(&tmp); return; } } /* Parsed successfully, so we can set the parsed precision and do the op. */ php_decimal_set_precision(res, prec); op(res, mpd1, mpd2); mpd_del(&tmp); } /******************************************************************************/ /* SERIALIZATION */ /******************************************************************************/ /** * Serialize */ static php_success_t php_decimal_serialize(zval *object, unsigned char **buffer, size_t *length, zend_serialize_data *data) { zval tmp; smart_str buf = {0}; php_decimal_t *obj = Z_DECIMAL_P(object); php_serialize_data_t serialize_data = (php_serialize_data_t) data; PHP_VAR_SERIALIZE_INIT(serialize_data); /* Serialize the internal value as a string. */ ZVAL_STR(&tmp, php_decimal_to_string(obj)); php_var_serialize(&buf, &tmp, &serialize_data); zval_ptr_dtor(&tmp); /* Serialize the precision as an integer. */ ZVAL_LONG(&tmp, php_decimal_get_precision(obj)); php_var_serialize(&buf, &tmp, &serialize_data); PHP_VAR_SERIALIZE_DESTROY(serialize_data); *buffer = (unsigned char *) estrndup(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)); *length = ZSTR_LEN(buf.s); smart_str_free(&buf); return SUCCESS; } /** * Unserialize */ static php_success_t php_decimal_unserialize(zval *obj, zend_class_entry *ce, const unsigned char *buffer, size_t length, zend_unserialize_data *data) { zval *value; zval *prec; php_decimal_t *res = php_decimal(); php_unserialize_data_t unserialize_data = (php_unserialize_data_t) data; const unsigned char *pos = buffer; const unsigned char *end = buffer + length; PHP_VAR_UNSERIALIZE_INIT(unserialize_data); /* Unserialize internal decimal value, which was serialized as a string. */ value = var_tmp_var(&unserialize_data); if (!php_var_unserialize(value, &pos, end, &unserialize_data) || Z_TYPE_P(value) != IS_STRING) { goto error; } /* Unserialize precision, which was serialized as an integer. */ prec = var_tmp_var(&unserialize_data); if (!php_var_unserialize(prec, &pos, end, &unserialize_data) || Z_TYPE_P(prec) != IS_LONG) { goto error; } /* Check that we've parsed the entire serialized string. */ if (pos != end) { goto error; } /* Check precision is valid. */ if (php_decimal_precision_is_valid(Z_LVAL_P(prec)) == false) { php_decimal_precision_out_of_range(Z_LVAL_P(prec)); goto error; } /* Set the precision. */ php_decimal_set_precision(res, Z_LVAL_P(prec)); /* Attempt to parse the unserialized string, quietly, delegate to local error. */ if (php_decimal_mpd_set_string(PHP_DECIMAL_MPD(res), Z_STR_P(value), Z_LVAL_P(prec), true) == FAILURE) { goto error; } /* Success! Set as zval and return. */ ZVAL_DECIMAL(obj, res); PHP_VAR_UNSERIALIZE_DESTROY(unserialize_data); return SUCCESS; error: php_decimal_release(res); PHP_VAR_UNSERIALIZE_DESTROY(unserialize_data); php_decimal_unserialize_error(); return FAILURE; } /******************************************************************************/ /* COMPARISON */ /******************************************************************************/ /** * Normalizes the result of a decimal comparison to be either -1, 0 or 1. This * is necessary because anything compared to NAN should be 1, even when operands * are flipped ie op2 <=> op1. */ static int php_decimal_normalize_compare_result(int result, int invert) { switch (result) { case 0: case 1: case -1: return invert ? -result : result; case PHP_DECIMAL_COMPARE_NAN: return 1; case PHP_DECIMAL_COMPARE_UNKNOWN: return invert ? -1 : 1; /* Should not be possible. */ default: return 1; } } /** * Attempt to compare op1 and op2. It's possible that the comparison is not * defined (when comparing to NAN), so we should return the special comparison * flag that indicates an undefined result. Returning 1 here is no good because * operations like "greater than" would be true for NAN. */ static int php_decimal_compare_mpd(mpd_t *op1, mpd_t *op2) { uint32_t status = 0; int result = mpd_qcmp(op1, op2, &status); if (UNEXPECTED(status & MPD_Invalid_operation)) { return PHP_DECIMAL_COMPARE_NAN; } return result; } /** * Compares two decimals using value-only comparison, precision is ignored. */ static int php_decimal_compare(php_decimal_t *op1, php_decimal_t *op2) { int result = php_decimal_compare_mpd(PHP_DECIMAL_MPD(op1), PHP_DECIMAL_MPD(op2)); if (result == 0) { zend_long prec1 = php_decimal_get_precision(op1); zend_long prec2 = php_decimal_get_precision(op2); return prec1 == prec2 ? 0 : (prec1 < prec2 ? -1 : 1); } return result; } /** * Compares a decimal object to a double. */ static int php_decimal_compare_to_double(php_decimal_t *obj, double dval) { if (UNEXPECTED(zend_isnan(dval))) { return PHP_DECIMAL_COMPARE_NAN; } else { int result; PHP_DECIMAL_TEMP_MPD(tmp); php_decimal_mpd_set_double(&tmp, dval); result = php_decimal_compare_mpd(PHP_DECIMAL_MPD(obj), &tmp); mpd_del(&tmp); return result; } } /** * Compares a decimal to a non-decimal zval. */ static int php_decimal_compare_to_scalar(php_decimal_t *obj, zval *op2) { while (1) { switch (Z_TYPE_P(op2)) { case IS_NULL: case IS_FALSE: return 1; case IS_TRUE: return 0; /* Allow comparing to float. */ case IS_DOUBLE: return php_decimal_compare_to_double(obj, Z_DVAL_P(op2)); /* TODO not sure if this is necessary... */ case IS_REFERENCE: op2 = Z_REFVAL_P(op2); continue; /* Attempt to parse the value, then compare. */ /* Return unknown on failure to avoid false "greater than" indicator. */ default: { int result = PHP_DECIMAL_COMPARE_UNKNOWN; PHP_DECIMAL_TEMP_MPD(tmp); if (EXPECTED(php_decimal_parse_scalar_quiet(&tmp, op2, PHP_DECIMAL_MAX_PREC) == SUCCESS)) { result = php_decimal_compare_mpd(PHP_DECIMAL_MPD(obj), &tmp); } mpd_del(&tmp); return result; } } } } /** * Compares a decimal to a zval that could also be a decimal. */ static int php_decimal_compare_to_zval(php_decimal_t *op1, zval *op2) { if (Z_IS_DECIMAL_P(op2)) { return php_decimal_compare(op1, Z_DECIMAL_P(op2)); } return php_decimal_compare_to_scalar(op1, op2); } /******************************************************************************/ /* OBJECT HANDLERS */ /******************************************************************************/ /** * Compares two zval's, one of which must be a decimal. This is the function * used by the compare handler, as well as compareTo. */ #if PHP_VERSION_ID >= 80000 static int php_decimal_compare_zval_to_zval(zval *op1, zval *op2) { int result; int invert; if (Z_IS_DECIMAL_P(op1)) { result = php_decimal_compare_to_zval(Z_DECIMAL_P(op1), op2); invert = 0; } else { result = php_decimal_compare_to_zval(Z_DECIMAL_P(op2), op1); invert = 1; } return php_decimal_normalize_compare_result(result, invert); } #else static php_success_t php_decimal_compare_zval_to_zval(zval *retval, zval *op1, zval *op2) { int result; int invert; if (Z_IS_DECIMAL_P(op1)) { result = php_decimal_compare_to_zval(Z_DECIMAL_P(op1), op2); invert = 0; } else { result = php_decimal_compare_to_zval(Z_DECIMAL_P(op2), op1); invert = 1; } ZVAL_LONG(retval, php_decimal_normalize_compare_result(result, invert)); return SUCCESS; } #endif /** * Compares decimal between two zvals/decimals. This is the function used as between. */ static php_success_t php_decimal_compare_between_left_and_right(zval *retval, zval *target, zval *leftOp, zval *rightOp) { int resultLeft; int resultRight; if (Z_IS_DECIMAL_P(leftOp)) { resultLeft = php_decimal_compare(Z_DECIMAL_P(target), Z_DECIMAL_P(leftOp)); } else { resultLeft = php_decimal_compare_to_zval(Z_DECIMAL_P(target), leftOp); } if (resultLeft == -1) { ZVAL_BOOL(retval, 0); return SUCCESS; } if (Z_IS_DECIMAL_P(rightOp)) { resultRight = php_decimal_compare(Z_DECIMAL_P(target), Z_DECIMAL_P(rightOp)); } else { resultRight = php_decimal_compare_to_zval(Z_DECIMAL_P(target), rightOp); } if (resultRight == 1) { ZVAL_BOOL(retval, 0); return SUCCESS; } ZVAL_BOOL(retval, 1); return SUCCESS; } /** * var_dump, print_r etc. */ #if PHP_VERSION_ID >= 80000 static HashTable *php_decimal_get_debug_info(zend_object *obj, int *is_temp) { zval tmp; HashTable *debug_info; ALLOC_HASHTABLE(debug_info); zend_hash_init(debug_info, 2, NULL, ZVAL_PTR_DTOR, 0); ZVAL_STR(&tmp, php_decimal_to_string(O_DECIMAL_P(obj))); zend_hash_str_update(debug_info, "value", sizeof("value") - 1, &tmp); ZVAL_LONG(&tmp, php_decimal_get_precision(O_DECIMAL_P(obj))); zend_hash_str_update(debug_info, "precision", sizeof("precision") - 1, &tmp); *is_temp = 1; return debug_info; } #else static HashTable *php_decimal_get_debug_info(zval *obj, int *is_temp) { zval tmp; HashTable *debug_info; ALLOC_HASHTABLE(debug_info); zend_hash_init(debug_info, 2, NULL, ZVAL_PTR_DTOR, 0); ZVAL_STR(&tmp, php_decimal_to_string(Z_DECIMAL_P(obj))); zend_hash_str_update(debug_info, "value", sizeof("value") - 1, &tmp); ZVAL_LONG(&tmp, php_decimal_get_precision(Z_DECIMAL_P(obj))); zend_hash_str_update(debug_info, "precision", sizeof("precision") - 1, &tmp); *is_temp = 1; return debug_info; } #endif /** * Cast to string, int, float or bool. */ #if PHP_VERSION_ID >= 80000 static php_success_t php_decimal_cast_object(zend_object *obj, zval *result, int type) { switch (type) { case IS_STRING: ZVAL_STR(result, php_decimal_to_string(O_DECIMAL_P(obj))); return SUCCESS; case IS_LONG: ZVAL_LONG(result, php_decimal_to_long(O_DECIMAL_P(obj))); return SUCCESS; case IS_DOUBLE: ZVAL_DOUBLE(result, php_decimal_to_double(O_DECIMAL_P(obj))); return SUCCESS; case _IS_BOOL: ZVAL_BOOL(result, 1); /* Objects are always true */ return SUCCESS; default: return FAILURE; } } #else static php_success_t php_decimal_cast_object(zval *obj, zval *result, int type) { switch (type) { case IS_STRING: ZVAL_STR(result, php_decimal_to_string(Z_DECIMAL_P(obj))); return SUCCESS; case IS_LONG: ZVAL_LONG(result, php_decimal_to_long(Z_DECIMAL_P(obj))); return SUCCESS; case IS_DOUBLE: ZVAL_DOUBLE(result, php_decimal_to_double(Z_DECIMAL_P(obj))); return SUCCESS; case _IS_BOOL: ZVAL_BOOL(result, 1); /* Objects are always true */ return SUCCESS; default: return FAILURE; } } #endif /** * Operator overloading. * * We don't know which of op1 and op2 is a decimal object (if not both). */ static php_success_t php_decimal_do_operation(zend_uchar opcode, zval *result, zval *op1, zval *op2) { zval op1_copy; php_decimal_binary_op_t op = php_decimal_get_operation_for_opcode(opcode); /* Unsupported op type. */ if (UNEXPECTED(op == NULL)) { return FAILURE; } /* This allows for assign syntax, ie. $op1 /= $op2 */ if (op1 == result) { ZVAL_COPY_VALUE(&op1_copy, op1); op1 = &op1_copy; } /* Attempt operation. */ ZVAL_DECIMAL(result, php_decimal()); php_decimal_do_binary_op(op, Z_DECIMAL_P(result), op1, op2); /** * Something went wrong so unset the result, but we don't want the engine to * carry on trying to cast the decimal, so we return success. */ if (UNEXPECTED(EG(exception))) { zval_ptr_dtor(result); ZVAL_UNDEF(result); return SUCCESS; } if (op1 == &op1_copy) { zval_dtor(op1); } return SUCCESS; } #if PHP_VERSION_ID >= 80000 /** * Object property read - not supported. */ static zval *php_decimal_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { php_decimal_object_properties_not_supported(); return &EG(uninitialized_zval); } /** * Object property write - not supported. */ static zval* php_decimal_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot) { php_decimal_object_properties_not_supported(); return &EG(uninitialized_zval); } /** * Object property isset/empty - not supported. */ static int php_decimal_has_property(zend_object *object, zend_string *member, int has_set_exists, void **cache_slot) { php_decimal_object_properties_not_supported(); return 0; } /** * Object property unset - not supported. */ static void php_decimal_unset_property(zend_object *object, zend_string *member, void **cache_slot) { php_decimal_object_properties_not_supported(); } #else /** * Object property read - not supported. */ static zval *php_decimal_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) { php_decimal_object_properties_not_supported(); return &EG(uninitialized_zval); } /** * Object property write - not supported. */ static zval *php_decimal_write_property(zval *object, zval *member, zval *value, void **cache_slot) { php_decimal_object_properties_not_supported(); return &EG(uninitialized_zval); } /** * Object property isset/empty - not supported. */ static int php_decimal_has_property(zval *object, zval *member, int has_set_exists, void **cache_slot) { php_decimal_object_properties_not_supported(); return 0; } /** * Object property unset - not supported. */ static void php_decimal_unset_property(zval *object, zval *member, void **cache_slot) { php_decimal_object_properties_not_supported(); } #endif /******************************************************************************/ /* PARAMETER PARSING */ /******************************************************************************/ /** * No parameters expected, bail out if there were some. */ #define PHP_DECIMAL_PARAMS_PARSE_NONE() \ if (zend_parse_parameters_none() == FAILURE) { \ return; \ } /** * Parse a binary operation (op1 OP op2). */ #define PHP_DECIMAL_PARSE_BINARY_OP(op) do { \ php_decimal_t *res = php_decimal(); \ zval *op2 = NULL; \ \ ZEND_PARSE_PARAMETERS_START(1, 1) \ Z_PARAM_ZVAL(op2) \ ZEND_PARSE_PARAMETERS_END(); \ php_decimal_do_binary_op(op, res, getThis(), op2); \ RETURN_DECIMAL(res); \ } while (0) /** * Parse a unary operation (OP op1). */ #define PHP_DECIMAL_PARSE_UNARY_OP(op) do { \ php_decimal_t *obj = THIS_DECIMAL(); \ php_decimal_t *res = php_decimal_with_prec(php_decimal_get_precision(obj)); \ \ PHP_DECIMAL_PARAMS_PARSE_NONE(); \ \ op(res, PHP_DECIMAL_MPD(obj)); \ RETURN_DECIMAL(res); \ } while(0) /******************************************************************************/ /* PHP CLASS METHODS */ /******************************************************************************/ /** * Decimal::__construct */ PHP_DECIMAL_ARGINFO(__construct, 0) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_LONG(precision) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(__construct) { zval *value = NULL; zend_long prec = 0; /* Check if already constructed, because decimals are immutable */ if (PHP_DECIMAL_IS_INITIALIZED(THIS_DECIMAL())) { php_decimal_constructor_already_called(); return; } ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_ZVAL(value) #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(prec) #else Z_PARAM_STRICT_LONG(prec) #endif ZEND_PARSE_PARAMETERS_END(); { php_decimal_t *obj = THIS_DECIMAL(); /* No value or precision given: init using default precision, set zero. */ if (value == NULL) { php_decimal_init(obj); php_decimal_set_precision(obj, PHP_DECIMAL_DEFAULT_PRECISION); php_decimal_set_zero(obj); /* Value given, but not precision: init using default precision, parse. */ } else if (ZEND_NUM_ARGS() == 1) { php_decimal_init(obj); php_decimal_set_precision(obj, PHP_DECIMAL_DEFAULT_PRECISION); php_decimal_parse_into(obj, value); /* Both value and precision given: validate, init, parse. */ } else { PHP_DECIMAL_VALID_PRECISION_OR_RETURN(prec); php_decimal_init(obj); php_decimal_set_precision(obj, prec); php_decimal_parse_into(obj, value); } } } /** * Decimal::add */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(add, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(add) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_add); } /** * Decimal::sub */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(sub, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(sub) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_sub); } /** * Decimal::mul */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(mul, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(mul) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_mul); } /** * Decimal::div */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(div, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(div) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_div); } /** * Decimal::mod */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(mod, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(mod) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_mod); } /** * Decimal::pow */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(pow, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(pow) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_pow); } /** * Decimal::rem */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(rem, 1) PHP_DECIMAL_ARGINFO_ZVAL(value) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(rem) { PHP_DECIMAL_PARSE_BINARY_OP(php_decimal_rem); } /** * Decimal::ln * Decimal::log */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(ln, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(ln) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_ln); } /** * Decimal::exp */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(exp, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(exp) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_exp); } /** * Decimal::log10 */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(log10, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(log10) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_log10); } /** * Decimal::sqrt */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(sqrt, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(sqrt) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_sqrt); } /** * Decimal::round */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(round, 0) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(places) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(rounding) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(round) { php_decimal_t *obj = THIS_DECIMAL(); php_decimal_t *res = php_decimal_with_prec(php_decimal_get_precision(obj)); zend_long places = 0; zend_long rounding = PHP_DECIMAL_DEFAULT_ROUNDING; ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(places) Z_PARAM_LONG(rounding) #else Z_PARAM_STRICT_LONG(places) Z_PARAM_STRICT_LONG(rounding) #endif ZEND_PARSE_PARAMETERS_END(); php_decimal_round_mpd(PHP_DECIMAL_MPD(res), PHP_DECIMAL_MPD(obj), places, rounding); RETURN_DECIMAL(res); } /** * Decimal::floor */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(floor, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(floor) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_floor); } /** * Decimal::ceil */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(ceil, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(ceil) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_ceil); } /** * Decimal::truncate */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(truncate, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(truncate) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_truncate); } /** * Decimal::shift */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(shift, 1) PHP_DECIMAL_ARGINFO_LONG(places) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(shift) { php_decimal_t *obj = THIS_DECIMAL(); php_decimal_t *res = php_decimal_with_prec(php_decimal_get_precision(obj)); zend_long places = 0; ZEND_PARSE_PARAMETERS_START(1, 1) #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(places) #else Z_PARAM_STRICT_LONG(places) #endif ZEND_PARSE_PARAMETERS_END(); php_decimal_shift(res, PHP_DECIMAL_MPD(obj), places); RETURN_DECIMAL(res); } /** * Decimal::trim */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(trim, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(trim) { php_decimal_t *obj = THIS_DECIMAL(); php_decimal_t *res = php_decimal_create_copy(obj); PHP_DECIMAL_PARAMS_PARSE_NONE(); php_decimal_trim_trailing_zeroes(res); RETURN_DECIMAL(res); } /** * Decimal::precision */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(precision, IS_LONG, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(precision) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_LONG(php_decimal_get_precision(THIS_DECIMAL())); } /** * Decimal::signum */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(signum, IS_LONG, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(signum) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_LONG(php_decimal_signum(THIS_MPD())); } /** * Decimal::parity */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(parity, IS_LONG, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(parity) { PHP_DECIMAL_PARAMS_PARSE_NONE(); if (UNEXPECTED(mpd_isspecial(THIS_MPD()))) { RETURN_LONG(1); } else { PHP_DECIMAL_TEMP_MPD(tmp); mpd_trunc(&tmp, THIS_MPD(), php_decimal_context()); RETVAL_LONG(mpd_isodd(&tmp)); mpd_del(&tmp); } } /** * Decimal::abs */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(abs, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(abs) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_abs); } /** * Decimal::negate */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(negate, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(negate) { PHP_DECIMAL_PARSE_UNARY_OP(php_decimal_negate); } /** * Decimal::isEven */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isEven, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isEven) { mpd_t *mpd = THIS_MPD(); PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_isinteger(mpd) && !mpd_isodd(mpd)); } /** * Decimal::isOdd */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isOdd, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isOdd) { mpd_t *mpd = THIS_MPD(); PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_isinteger(mpd) && mpd_isodd(mpd)); } /** * Decimal::isPositive */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isPositive, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isPositive) { PHP_DECIMAL_PARAMS_PARSE_NONE(); mpd_t *mpd = THIS_MPD(); RETURN_BOOL(!mpd_isnan(mpd) && mpd_ispositive(mpd)); } /** * Decimal::isNegative */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isNegative, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isNegative) { PHP_DECIMAL_PARAMS_PARSE_NONE(); mpd_t *mpd = THIS_MPD(); RETURN_BOOL(!mpd_isnan(mpd) && mpd_isnegative(mpd)); } /** * Decimal::isNaN */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isNaN, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isNaN) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_isqnan(THIS_MPD())); } /** * Decimal::isInf */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isInf, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isInf) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_isinfinite(THIS_MPD())); } /** * Decimal::isInteger */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isInteger, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isInteger) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_isinteger(THIS_MPD())); } /** * Decimal::isZero */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(isZero, _IS_BOOL, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(isZero) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_BOOL(mpd_iszero(THIS_MPD())); } /** * Decimal::toFixed */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(toFixed, IS_STRING, 0) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(places) PHP_DECIMAL_ARGINFO_OPTIONAL_BOOL(commas) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(rounding) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(toFixed) { zend_long places = 0; zend_bool commas = false; zend_long rounding = PHP_DECIMAL_DEFAULT_ROUNDING; ZEND_PARSE_PARAMETERS_START(0, 3) Z_PARAM_OPTIONAL #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(places) #else Z_PARAM_STRICT_LONG(places) #endif Z_PARAM_BOOL(commas) #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(rounding) #else Z_PARAM_STRICT_LONG(rounding) #endif ZEND_PARSE_PARAMETERS_END(); RETURN_STR(php_decimal_format(THIS_DECIMAL(), places, commas, rounding)); } /** * Decimal::__toString * Decimal::toString * Decimal::jsonSerialize */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(toString, IS_STRING, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(toString) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_STR(php_decimal_to_string(THIS_DECIMAL())); } /** * Decimal::toInt */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(toInt, IS_LONG, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(toInt) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_LONG(php_decimal_to_long(THIS_DECIMAL())); } /** * Decimal::toFloat */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(toFloat, IS_DOUBLE, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(toFloat) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_DOUBLE(php_decimal_to_double(THIS_DECIMAL())); } /** * Decimal::copy */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(copy, 0) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(copy) { PHP_DECIMAL_PARAMS_PARSE_NONE(); RETURN_DECIMAL(php_decimal_create_copy(THIS_DECIMAL())); } /** * Decimal::equals */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(equals, _IS_BOOL, 1) PHP_DECIMAL_ARGINFO_ZVAL(other) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(equals) { zval *op2 = NULL; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(op2) ZEND_PARSE_PARAMETERS_END(); RETURN_BOOL(php_decimal_compare_to_zval(THIS_DECIMAL(), op2) == 0); } /** * Decimal::compareTo */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(compareTo, IS_LONG, 1) PHP_DECIMAL_ARGINFO_ZVAL(other) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(compareTo) { zval *op2 = NULL; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(op2) ZEND_PARSE_PARAMETERS_END(); #if PHP_VERSION_ID >= 80000 RETURN_LONG(php_decimal_compare_zval_to_zval(getThis(), op2)); #else php_decimal_compare_zval_to_zval(return_value, getThis(), op2); #endif } /** * Decimal::between */ PHP_DECIMAL_ARGINFO_RETURN_TYPE(between, _IS_BOOL, 1) PHP_DECIMAL_ARGINFO_ZVAL(op1) PHP_DECIMAL_ARGINFO_ZVAL(op2) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(between) { zval *opLeft = NULL; zval *opRight = NULL; ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_ZVAL(opLeft) Z_PARAM_ZVAL(opRight) ZEND_PARSE_PARAMETERS_END(); php_decimal_compare_between_left_and_right(return_value, getThis(), opLeft, opRight); } /** * Decimal::sum */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(sum, 1) PHP_DECIMAL_ARGINFO_ZVAL(values) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(precision) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(sum) { php_decimal_t *res = php_decimal(); zval *values = NULL; zend_long prec = PHP_DECIMAL_DEFAULT_PRECISION; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(values) Z_PARAM_OPTIONAL #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(prec) #else Z_PARAM_STRICT_LONG(prec) #endif ZEND_PARSE_PARAMETERS_END(); PHP_DECIMAL_VALID_PRECISION_OR_RETURN(prec); php_decimal_set_precision(res, prec); php_decimal_sum(res, values); RETURN_DECIMAL(res); } /** * Decimal::avg */ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(avg, 1) PHP_DECIMAL_ARGINFO_ZVAL(values) PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(precision) PHP_DECIMAL_ARGINFO_END() PHP_DECIMAL_METHOD(avg) { php_decimal_t *res = php_decimal(); zval *values = NULL; zend_long prec = PHP_DECIMAL_DEFAULT_PRECISION; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ZVAL(values) Z_PARAM_OPTIONAL #if PHP_VERSION_ID >= 80000 Z_PARAM_LONG(prec) #else Z_PARAM_STRICT_LONG(prec) #endif ZEND_PARSE_PARAMETERS_END(); PHP_DECIMAL_VALID_PRECISION_OR_RETURN(prec); php_decimal_set_precision(res, prec); php_decimal_avg(res, values); RETURN_DECIMAL(res); } /******************************************************************************/ /* CLASS ENTRY */ /******************************************************************************/ static zend_function_entry decimal_methods[] = { PHP_DECIMAL_ME(__construct) PHP_DECIMAL_ME(copy) PHP_DECIMAL_ME(add) PHP_DECIMAL_ME(sub) PHP_DECIMAL_ME(mul) PHP_DECIMAL_ME(div) PHP_DECIMAL_ME(rem) PHP_DECIMAL_ME(mod) PHP_DECIMAL_ME(pow) PHP_DECIMAL_ME(ln) PHP_DECIMAL_ME(exp) PHP_DECIMAL_ME(log10) PHP_DECIMAL_ME(sqrt) PHP_DECIMAL_ME(floor) PHP_DECIMAL_ME(ceil) PHP_DECIMAL_ME(truncate) PHP_DECIMAL_ME(round) PHP_DECIMAL_ME(shift) PHP_DECIMAL_ME(trim) PHP_DECIMAL_ME(precision) PHP_DECIMAL_ME(signum) PHP_DECIMAL_ME(parity) PHP_DECIMAL_ME(abs) PHP_DECIMAL_ME(negate) PHP_DECIMAL_ME(isEven) PHP_DECIMAL_ME(isOdd) PHP_DECIMAL_ME(isPositive) PHP_DECIMAL_ME(isNegative) PHP_DECIMAL_ME(isNaN) PHP_DECIMAL_ME(isInf) PHP_DECIMAL_ME(isInteger) PHP_DECIMAL_ME(isZero) PHP_DECIMAL_ME(toFixed) PHP_DECIMAL_ME(toString) PHP_DECIMAL_ME(toInt) PHP_DECIMAL_ME(toFloat) PHP_DECIMAL_ME(equals) PHP_DECIMAL_ME(compareTo) PHP_DECIMAL_ME(between) /* Static methods */ PHP_DECIMAL_STATIC_ME(sum) PHP_DECIMAL_STATIC_ME(avg) /* Aliases: Alias Defined */ PHP_DECIMAL_AL(__toString, toString) PHP_DECIMAL_AL(jsonSerialize, toString) PHP_FE_END }; /** * Decimal functions. */ static zend_function_entry decimal_functions[] = { PHP_FE_END }; /** * Sets the object handlers. */ static void php_decimal_register_class_handlers() { memcpy(&php_decimal_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); /** * No need for an offset because we're casting back and forth between * zend_object and php_decimal. Malloc should know the size of the block so * when the engine frees the zend_object, it will free the php_decimal. We * also don't have any class properties and the class is final. */ php_decimal_handlers.offset = 0; php_decimal_handlers.free_obj = php_decimal_free_object; php_decimal_handlers.clone_obj = php_decimal_clone_obj; php_decimal_handlers.cast_object = php_decimal_cast_object; php_decimal_handlers.compare = php_decimal_compare_zval_to_zval; php_decimal_handlers.do_operation = php_decimal_do_operation; php_decimal_handlers.get_debug_info = php_decimal_get_debug_info; php_decimal_handlers.read_property = php_decimal_read_property; php_decimal_handlers.write_property = php_decimal_write_property; php_decimal_handlers.has_property = php_decimal_has_property; php_decimal_handlers.unset_property = php_decimal_unset_property; } /** * Registers the class entry and constants. */ static void php_decimal_register_class_entry() { zend_class_entry ce; INIT_CLASS_ENTRY(ce, PHP_DECIMAL_FQCN, decimal_methods); php_decimal_ce = zend_register_internal_class(&ce); php_decimal_ce->ce_flags |= ZEND_ACC_FINAL; php_decimal_ce->create_object = php_decimal_create_object; php_decimal_ce->serialize = php_decimal_serialize; php_decimal_ce->unserialize = php_decimal_unserialize; #if PHP_VERSION_ID >= 80300 php_decimal_ce->default_object_handlers = &php_decimal_handlers; #endif zend_class_implements(php_decimal_ce, 1, php_json_serializable_ce); PHP_DECIMAL_LONG_CONSTANT("ROUND_UP", PHP_DECIMAL_ROUND_UP); PHP_DECIMAL_LONG_CONSTANT("ROUND_DOWN", PHP_DECIMAL_ROUND_DOWN); PHP_DECIMAL_LONG_CONSTANT("ROUND_CEILING", PHP_DECIMAL_ROUND_CEILING); PHP_DECIMAL_LONG_CONSTANT("ROUND_FLOOR", PHP_DECIMAL_ROUND_FLOOR); PHP_DECIMAL_LONG_CONSTANT("ROUND_HALF_UP", PHP_DECIMAL_ROUND_HALF_UP); PHP_DECIMAL_LONG_CONSTANT("ROUND_HALF_DOWN", PHP_DECIMAL_ROUND_HALF_DOWN); PHP_DECIMAL_LONG_CONSTANT("ROUND_HALF_EVEN", PHP_DECIMAL_ROUND_HALF_EVEN); PHP_DECIMAL_LONG_CONSTANT("ROUND_HALF_ODD", PHP_DECIMAL_ROUND_HALF_ODD); PHP_DECIMAL_LONG_CONSTANT("ROUND_TRUNCATE", PHP_DECIMAL_ROUND_TRUNCATE); PHP_DECIMAL_LONG_CONSTANT("DEFAULT_PRECISION", PHP_DECIMAL_DEFAULT_PRECISION); PHP_DECIMAL_LONG_CONSTANT("DEFAULT_ROUNDING", PHP_DECIMAL_DEFAULT_ROUNDING); PHP_DECIMAL_LONG_CONSTANT("MIN_PRECISION", PHP_DECIMAL_MIN_PREC); PHP_DECIMAL_LONG_CONSTANT("MAX_PRECISION", PHP_DECIMAL_MAX_PREC); } /******************************************************************************/ /* INI */ /******************************************************************************/ PHP_INI_BEGIN() PHP_INI_END() /******************************************************************************/ /* MODULE */ /******************************************************************************/ ZEND_DECLARE_MODULE_GLOBALS(decimal) static void php_decimal_init_globals(zend_decimal_globals *g) { memset(g, 0, sizeof(zend_decimal_globals)); } /** * Module entry */ zend_module_entry decimal_module_entry = { STANDARD_MODULE_HEADER, PHP_DECIMAL_EXTNAME, decimal_functions, PHP_MINIT(decimal), PHP_MSHUTDOWN(decimal), PHP_RINIT(decimal), PHP_RSHUTDOWN(decimal), PHP_MINFO(decimal), PHP_DECIMAL_VERSION, STANDARD_MODULE_PROPERTIES }; /** * Module information displayed by phpinfo() */ PHP_MINFO_FUNCTION(decimal) { php_info_print_table_start(); php_info_print_table_row(2, "decimal support", "enabled"); php_info_print_table_row(2, "decimal version", PHP_DECIMAL_VERSION); php_info_print_table_row(2, "libmpdec version", MPD_VERSION); php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } /** * Set custom allocators. */ static void *php_decimal_mpd_malloc(size_t size) { return emalloc(size); } static void *php_decimal_mpd_calloc(size_t nmemb, size_t size) { return ecalloc(nmemb, size); } static void *php_decimal_mpd_realloc(void *ptr, size_t size) { return erealloc(ptr, size); } static void php_decimal_mpd_free(void *ptr) { efree(ptr); } /** * The second Opcache pass will convert numeric string constants to float, * so statements like `$decimal * "0.75"` will throw because floats are not * supported. Otherwise, this conversion will be transparent which means you * are using float internally when your code uses a string. * * Disabling it gives us guaranteed consistency at a small performance cost. */ static void php_decimal_disable_opcache_pass2() { zend_long level = INI_INT("opcache.optimization_level"); if (level) { zend_string *key = zend_string_init(ZEND_STRL("opcache.optimization_level"), 1); zend_string *val = strpprintf(0, "0x%08X", (unsigned int) (level & ~2)); zend_alter_ini_entry(key, val, ZEND_INI_SYSTEM, ZEND_INI_STAGE_ACTIVATE); zend_string_release(key); zend_string_release(val); } } /** * Module startup */ PHP_MINIT_FUNCTION(decimal) { php_decimal_register_class_entry(); php_decimal_register_class_handlers(); ZEND_INIT_MODULE_GLOBALS(decimal, php_decimal_init_globals, NULL); /* Set guaranteed minimum number of coefficient words based on default prec. */ mpd_setminalloc(2 * ((PHP_DECIMAL_DEFAULT_PRECISION + MPD_RDIGITS - 1) / MPD_RDIGITS)); /* Set custom memory allocation and trap handler functions. */ mpd_callocfunc = php_decimal_mpd_calloc; mpd_mallocfunc = php_decimal_mpd_malloc; mpd_reallocfunc = php_decimal_mpd_realloc; mpd_free = php_decimal_mpd_free; mpd_traphandler = php_decimal_mpd_traphandler; return SUCCESS; } /** * Module shutdown */ PHP_MSHUTDOWN_FUNCTION(decimal) { return SUCCESS; } /** * Request startup */ PHP_RINIT_FUNCTION(decimal) { #if defined(COMPILE_DL_DECIMAL) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif php_decimal_disable_opcache_pass2(); /* Initialize the shared context */ mpd_defaultcontext(php_decimal_context()); /* Set default rounding */ mpd_qsettraps(php_decimal_context(), PHP_DECIMAL_TRAPS); mpd_qsetround(php_decimal_context(), PHP_DECIMAL_DEFAULT_ROUNDING); return SUCCESS; } /** * Request shutdown */ PHP_RSHUTDOWN_FUNCTION(decimal) { return SUCCESS; } #ifdef COMPILE_DL_DECIMAL #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE(); #endif ZEND_GET_MODULE(decimal) #endif decimal-1.5.0/php_decimal.h0000644000000000000000000002421514552656040014242 0ustar rootroot/** * The MIT License (MIT) * Copyright (c) 2018 Rudi Theunissen * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH * THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef HAVE_PHP_DECIMAL_H #define HAVE_PHP_DECIMAL_H #ifdef PHP_WIN32 # define PHP_DECIMAL_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_DECIMAL_API __attribute__ ((visibility("default"))) #else # define PHP_DECIMAL_API #endif #ifdef ZTS #include "TSRM.h" #endif #include #include #include "php.h" #include "zend_smart_str.h" #include "Zend/zend_exceptions.h" #include "Zend/zend_interfaces.h" #include "ext/standard/info.h" #include "ext/standard/php_var.h" #include "ext/standard/php_math.h" #include "ext/standard/php_string.h" #include "ext/json/php_json.h" #include "ext/spl/spl_exceptions.h" #include "mpdecimal.h" #define PHP_DECIMAL_VERSION "1.5.0" #define PHP_DECIMAL_EXTNAME "decimal" #define PHP_DECIMAL_CLASS "Decimal" #define PHP_DECIMAL_NAMESPACE "Decimal" #define PHP_DECIMAL_FQCN PHP_DECIMAL_NAMESPACE "\\" PHP_DECIMAL_CLASS /** * Special comparison result flags that are not -1, 0, or 1. */ #define PHP_DECIMAL_COMPARE_NAN 2 #define PHP_DECIMAL_COMPARE_UNKNOWN 3 /** * Rounding modes. */ #define PHP_DECIMAL_ROUND_UP 101 #define PHP_DECIMAL_ROUND_DOWN 102 #define PHP_DECIMAL_ROUND_CEILING 103 #define PHP_DECIMAL_ROUND_FLOOR 104 #define PHP_DECIMAL_ROUND_HALF_UP 105 #define PHP_DECIMAL_ROUND_HALF_DOWN 106 #define PHP_DECIMAL_ROUND_HALF_EVEN 107 #define PHP_DECIMAL_ROUND_HALF_ODD 108 #define PHP_DECIMAL_ROUND_TRUNCATE 109 /** * Defines which conditions call the trap handler. */ #define PHP_DECIMAL_TRAPS (MPD_Errors | MPD_Traps) /** * Defaults */ #define PHP_DECIMAL_DEFAULT_PRECISION 28 #define PHP_DECIMAL_DEFAULT_ROUNDING PHP_DECIMAL_ROUND_HALF_EVEN /** * TODO: Not sure if we need to free these with mpd_del(&name) */ #define PHP_DECIMAL_TEMP_MPD(name) \ mpd_uint_t name##_data[MPD_MINALLOC_MAX]; \ mpd_t name = {MPD_STATIC|MPD_STATIC_DATA, 0, 0, 0, MPD_MINALLOC_MAX, name##_data} \ /** * Checks if a decimal object has been constructed or otherwise intialized. */ #define PHP_DECIMAL_IS_INITIALIZED(d) (PHP_DECIMAL_MPD(d)->data != NULL) /** * Precision constants. */ #define PHP_DECIMAL_MIN_PREC ((zend_long) 1) #define PHP_DECIMAL_MAX_PREC ((zend_long) MIN(ZEND_LONG_MAX, MPD_MAX_PREC)) /** * Used to perform a task using a temporary precision. There is no need to reset * this back to what it was before because all operations will set the precision * first. We're also assuming that all bounds checking has been done on _prec. */ #define PHP_DECIMAL_WITH_PRECISION(_prec, task) do { \ php_decimal_context()->prec = _prec; \ task; \ } while(0) /** * Object and zval access */ #define PHP_DECIMAL_MPD(p) (&(p)->mpd) #define Z_DECIMAL_P(z) ((php_decimal_t*) Z_OBJ_P(z)) #define O_DECIMAL_P(o) ((php_decimal_t*) o) #define Z_DECIMAL_MPD_P(z) PHP_DECIMAL_MPD(Z_DECIMAL_P(z)) #define Z_IS_DECIMAL_P(d) (Z_TYPE_P(d) == IS_OBJECT && Z_OBJCE_P(d) == php_decimal_ce) #define ZVAL_DECIMAL(z, dec) ZVAL_OBJ(z, (zend_object*) dec) #define THIS_DECIMAL() Z_DECIMAL_P(getThis()) #define THIS_MPD() Z_DECIMAL_MPD_P(getThis()) #define RETURN_DECIMAL(dec) \ do { \ php_decimal_t *_dec = dec; \ if (_dec) { \ ZVAL_DECIMAL(return_value, _dec); \ } else { \ ZVAL_NULL(return_value); \ } \ return; \ } while(0) /** * Special numbers should use the 3-letter uppercase representation. This macro * checks if mpd is special, and returns a zend_string immediately if it is. */ #define PHP_DECIMAL_CHECK_SPECIAL_STRING_RETURN(mpd) do { \ if (UNEXPECTED(mpd_isspecial(mpd))) { \ if (mpd_isqnan(mpd)) { \ return zend_string_init("NAN", 3, 0); \ } else { \ return mpd_ispositive(mpd) \ ? zend_string_init( "INF", 3, 0) \ : zend_string_init("-INF", 4, 0); \ } \ } \ } while (0) /** * Class, method, and function entry */ #define PHP_DECIMAL_METHOD(name) \ PHP_METHOD(Decimal, name) #define PHP_DECIMAL_FUNCTION(name) \ PHP_FUNCTION(name) #define PHP_DECIMAL_STATIC_ME(name) \ PHP_ME(Decimal, name, php_decimal_method_arginfo_##name, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #define PHP_DECIMAL_ME(name) \ PHP_ME(Decimal, name, php_decimal_method_arginfo_##name, ZEND_ACC_PUBLIC) #define PHP_DECIMAL_FE(name) \ ZEND_NS_FE(PHP_DECIMAL_NAMESPACE, name, php_decimal_function_arginfo_##name) #define PHP_DECIMAL_AL(alias, name) \ PHP_MALIAS(Decimal, alias, name, php_decimal_method_arginfo_##name, ZEND_ACC_PUBLIC) #define PHP_DECIMAL_LONG_CONSTANT(name, value) \ zend_declare_class_constant_long(php_decimal_ce, name, sizeof(name) - 1, value); /** * Arginfo */ #define PHP_DECIMAL_ARGINFO_EX(name, required_num_args, entry_type) \ ZEND_BEGIN_ARG_INFO_EX(php_decimal_##entry_type##_arginfo_##name, 0, 0, required_num_args) #if PHP_VERSION_ID >= 80000 #define PHP_DECIMAL_ARGINFO_RETURN_DECIMAL_EX(name, required_num_args, entry_type) \ static const zend_internal_arg_info php_decimal_##entry_type##_arginfo_##name[] = { \ {(const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST(PHP_DECIMAL_FQCN, 0, 0), 0}, #elif PHP_VERSION_ID >= 70200 #define PHP_DECIMAL_ARGINFO_RETURN_DECIMAL_EX(name, required_num_args, entry_type) \ static const zend_internal_arg_info php_decimal_##entry_type##_arginfo_##name[] = { \ {(const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CLASS_CONST(PHP_DECIMAL_FQCN, 0), 0, 0}, #else #define PHP_DECIMAL_ARGINFO_RETURN_DECIMAL_EX(name, required_num_args, entry_type) \ static const zend_internal_arg_info php_decimal_##entry_type##_arginfo_##name[] = { \ {(const char*)(zend_uintptr_t)(required_num_args), PHP_DECIMAL_FQCN, IS_OBJECT, 0, 0, 0}, #endif #if PHP_VERSION_ID >= 70200 #define PHP_DECIMAL_ARGINFO_RETURN_TYPE_EX(name, type, required_num_args, entry_type) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_decimal_##entry_type##_arginfo_##name, 0, required_num_args, type, 0) #else #define PHP_DECIMAL_ARGINFO_RETURN_TYPE_EX(name, type, required_num_args, entry_type) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(php_decimal_##entry_type##_arginfo_##name, 0, required_num_args, type, 0, 0) #endif #if PHP_VERSION_ID >= 80000 #define PHP_DECIMAL_ARGINFO_DECIMAL(name) {#name, ZEND_TYPE_INIT_CLASS_CONST(PHP_DECIMAL_FQCN, 0, 0), 0}, #elif PHP_VERSION_ID >= 70200 #define PHP_DECIMAL_ARGINFO_DECIMAL(name) {#name, ZEND_TYPE_ENCODE_CLASS_CONST(PHP_DECIMAL_FQCN, 0), 0, 0}, #else #define PHP_DECIMAL_ARGINFO_DECIMAL(name) {#name, PHP_DECIMAL_FQCN, IS_OBJECT, 0, 0, 0}, #endif #define PHP_DECIMAL_ARGINFO(name, required_num_args) \ PHP_DECIMAL_ARGINFO_EX(name, required_num_args, method) #define PHP_DECIMAL_ARGINFO_RETURN_DECIMAL(name, required_num_args) \ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL_EX(name, required_num_args, method) #define PHP_DECIMAL_ARGINFO_RETURN_TYPE(name, type, required_num_args) \ PHP_DECIMAL_ARGINFO_RETURN_TYPE_EX(name, type, required_num_args, method) #define PHP_DECIMAL_FUNCTION_ARGINFO(name, required_num_args) \ PHP_DECIMAL_ARGINFO_EX(name, required_num_args, function) #define PHP_DECIMAL_FUNCTION_ARGINFO_RETURN_DECIMAL(name, required_num_args) \ PHP_DECIMAL_ARGINFO_RETURN_DECIMAL_EX(name, required_num_args, function) #define PHP_DECIMAL_FUNCTION_ARGINFO_RETURN_TYPE(name, type, required_num_args) \ PHP_DECIMAL_ARGINFO_RETURN_TYPE_EX(name, type, required_num_args, function) #define PHP_DECIMAL_ARGINFO_ZVAL(name) \ ZEND_ARG_INFO(0, name) #define PHP_DECIMAL_ARGINFO_LONG(name) \ ZEND_ARG_TYPE_INFO(0, name, IS_LONG, 0) #define PHP_DECIMAL_ARGINFO_FLOAT(name) \ ZEND_ARG_TYPE_INFO(0, name, IS_DOUBLE, 0) #define PHP_DECIMAL_ARGINFO_OPTIONAL_LONG(name) \ ZEND_ARG_TYPE_INFO(0, name, IS_LONG, 1) #define PHP_DECIMAL_ARGINFO_OPTIONAL_BOOL(name) \ ZEND_ARG_TYPE_INFO(0, name, _IS_BOOL, 1) #define PHP_DECIMAL_ARGINFO_END() \ ZEND_END_ARG_INFO() /** * Custom definitions. */ typedef int php_decimal_rounding_t; typedef int php_success_t; /** * PHP decimal object. */ typedef struct _php_decimal_t { zend_object std; /* Zend object, must be first! */ mpd_t mpd; /* Embedded MPD object */ zend_long prec; /* Precision */ } php_decimal_t; /** * Operation function definitions. */ typedef void (*php_decimal_binary_op_t)(php_decimal_t *res, mpd_t *op1, mpd_t *op2); typedef void (*php_decimal_unary_op_t)(php_decimal_t *res, mpd_t *op1); /** * Module and class entry */ extern zend_module_entry php_decimal_module_entry; extern zend_class_entry *php_decimal_ce; ZEND_MINIT_FUNCTION(decimal); ZEND_MSHUTDOWN_FUNCTION(decimal); ZEND_RINIT_FUNCTION(decimal); ZEND_RSHUTDOWN_FUNCTION(decimal); ZEND_MINFO_FUNCTION(decimal); ZEND_BEGIN_MODULE_GLOBALS(decimal) mpd_context_t ctx; mpd_t *pi; ZEND_END_MODULE_GLOBALS(decimal) #ifdef ZTS #define DECIMAL_G(v) TSRMG(decimal_globals_id, zend_decimal_globals *, v) #else #define DECIMAL_G(v) (decimal_globals.v) #endif ZEND_EXTERN_MODULE_GLOBALS(decimal) #if defined(ZTS) && defined(COMPILE_DL_DS) ZEND_TSRMLS_CACHE_EXTERN(); #endif #endif