aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/commerceguys/intl/src/Calculator.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/commerceguys/intl/src/Calculator.php')
-rw-r--r--vendor/commerceguys/intl/src/Calculator.php247
1 files changed, 247 insertions, 0 deletions
diff --git a/vendor/commerceguys/intl/src/Calculator.php b/vendor/commerceguys/intl/src/Calculator.php
new file mode 100644
index 000000000..e00a564c1
--- /dev/null
+++ b/vendor/commerceguys/intl/src/Calculator.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace CommerceGuys\Intl;
+
+/**
+ * Provides helpers for bcmath-based arithmetic.
+ *
+ * The bcmath extension provides support for arbitrary precision arithmetic,
+ * which does not suffer from the precision loses that make floating point
+ * arithmetic unsafe for eCommerce.
+ *
+ * Important: All numbers must be passed as strings.
+ */
+final class Calculator
+{
+ /**
+ * Adds the second number to the first number.
+ *
+ * @param string $first_number The first number.
+ * @param string $second_number The second number.
+ * @param int $scale The maximum number of digits after the
+ * decimal place. Any digit after $scale will
+ * be truncated.
+ *
+ * @return string The result.
+ */
+ public static function add($first_number, $second_number, $scale = 6)
+ {
+ self::assertNumberFormat($first_number);
+ self::assertNumberFormat($second_number);
+ $result = bcadd($first_number, $second_number, $scale);
+
+ return self::trim($result);
+ }
+
+ /**
+ * Subtracts the second number from the first number.
+ *
+ * @param string $first_number The first number.
+ * @param string $second_number The second number.
+ * @param int $scale The maximum number of digits after the
+ * decimal place. Any digit after $scale will
+ * be truncated.
+ *
+ * @return string The result.
+ */
+ public static function subtract($first_number, $second_number, $scale = 6)
+ {
+ self::assertNumberFormat($first_number);
+ self::assertNumberFormat($second_number);
+ $result = bcsub($first_number, $second_number, $scale);
+
+ return self::trim($result);
+ }
+
+ /**
+ * Multiplies the first number by the second number.
+ *
+ * @param string $first_number The first number.
+ * @param string $second_number The second number.
+ * @param int $scale The maximum number of digits after the
+ * decimal place. Any digit after $scale will
+ * be truncated.
+ *
+ * @return string The result.
+ */
+ public static function multiply($first_number, $second_number, $scale = 6)
+ {
+ self::assertNumberFormat($first_number);
+ self::assertNumberFormat($second_number);
+ $result = bcmul($first_number, $second_number, $scale);
+
+ return self::trim($result);
+ }
+
+ /**
+ * Divides the first number by the second number.
+ *
+ * @param string $first_number The first number.
+ * @param string $second_number The second number.
+ * @param int $scale The maximum number of digits after the
+ * decimal place. Any digit after $scale will
+ * be truncated.
+ *
+ * @return string The result.
+ */
+ public static function divide($first_number, $second_number, $scale = 6)
+ {
+ self::assertNumberFormat($first_number);
+ self::assertNumberFormat($second_number);
+ $result = bcdiv($first_number, $second_number, $scale);
+
+ return self::trim($result);
+ }
+
+ /**
+ * Calculates the next highest whole value of a number.
+ *
+ * @param string $number The number.
+ *
+ * @return string The result.
+ */
+ public static function ceil($number)
+ {
+ if (self::compare($number, 0) == 1) {
+ $result = bcadd($number, '1', 0);
+ } else {
+ $result = bcadd($number, '0', 0);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Calculates the next lowest whole value of a number.
+ *
+ * @param string $number The number.
+ *
+ * @return string The result.
+ */
+ public static function floor($number)
+ {
+ if (self::compare($number, 0) == 1) {
+ $result = bcadd($number, '0', 0);
+ } else {
+ $result = bcadd($number, '-1', 0);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Rounds the given number.
+ *
+ * Replicates PHP's support for rounding to the nearest even/odd number
+ * even if that number is decimal ($precision > 0).
+ *
+ * @param string $number The number.
+ * @param int $precision The number of decimals to round to.
+ * @param int $mode The rounding mode. One of the following constants:
+ * PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN,
+ * PHP_ROUND_HALF_EVEN, PHP_ROUND_HALF_ODD.
+ *
+ * @return string The rounded number.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function round($number, $precision = 0, $mode = PHP_ROUND_HALF_UP)
+ {
+ self::assertNumberFormat($number);
+ if (!is_numeric($precision) || $precision < 0) {
+ throw new \InvalidArgumentException('The provided precision should be a positive number');
+ }
+
+ // Round the number in both directions (up/down) before choosing one.
+ $rounding_increment = bcdiv('1', pow(10, $precision), $precision);
+ if (self::compare($number, '0') == 1) {
+ $rounded_up = bcadd($number, $rounding_increment, $precision);
+ } else {
+ $rounded_up = bcsub($number, $rounding_increment, $precision);
+ }
+ $rounded_down = bcsub($number, 0, $precision);
+ // The rounding direction is based on the first decimal after $precision.
+ $number_parts = explode('.', $number);
+ $decimals = !empty($number_parts[1]) ? $number_parts[1] : '0';
+ $relevant_decimal = isset($decimals[$precision]) ? $decimals[$precision] : 0;
+ if ($relevant_decimal < 5) {
+ $number = $rounded_down;
+ } elseif ($relevant_decimal == 5) {
+ if ($mode == PHP_ROUND_HALF_UP) {
+ $number = $rounded_up;
+ } elseif ($mode == PHP_ROUND_HALF_DOWN) {
+ $number = $rounded_down;
+ } elseif ($mode == PHP_ROUND_HALF_EVEN) {
+ $integer = bcmul($rounded_up, pow(10, $precision), 0);
+ $number = bcmod($integer, '2') == 0 ? $rounded_up : $rounded_down;
+ } elseif ($mode == PHP_ROUND_HALF_ODD) {
+ $integer = bcmul($rounded_up, pow(10, $precision), 0);
+ $number = bcmod($integer, '2') != 0 ? $rounded_up : $rounded_down;
+ }
+ } elseif ($relevant_decimal > 5) {
+ $number = $rounded_up;
+ }
+
+ return $number;
+ }
+
+ /**
+ * Compares the first number to the second number.
+ *
+ * @param string $first_number The first number.
+ * @param string $second_number The second number.
+ * @param int $scale The maximum number of digits after the
+ * decimal place. Any digit after $scale will
+ * be truncated.
+ *
+ * @return int 0 if both numbers are equal, 1 if the first one is greater,
+ * -1 otherwise.
+ */
+ public static function compare($first_number, $second_number, $scale = 6)
+ {
+ self::assertNumberFormat($first_number);
+ self::assertNumberFormat($second_number);
+
+ return bccomp($first_number, $second_number, $scale);
+ }
+
+ /**
+ * Trims the given number.
+ *
+ * By default bcmath returns numbers with the number of digits according
+ * to $scale. This means that bcadd('2', '2', 6) will return '4.00000'.
+ * Trimming the number removes the excess zeroes.
+ *
+ * @param string $number The number to trim.
+ *
+ * @return string The trimmed number.
+ */
+ public static function trim($number)
+ {
+ if (strpos($number, '.') != false) {
+ // The number is decimal, strip trailing zeroes.
+ // If no digits remain after the decimal point, strip it as well.
+ $number = rtrim($number, '0');
+ $number = rtrim($number, '.');
+ }
+
+ return $number;
+ }
+
+ /**
+ * Assert that the given number is a numeric string value.
+ *
+ * @param string $number The number to check.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function assertNumberFormat($number)
+ {
+ if (is_float($number)) {
+ throw new \InvalidArgumentException(sprintf('The provided value "%s" must be a string, not a float.', $number));
+ }
+ if (!is_numeric($number)) {
+ throw new \InvalidArgumentException(sprintf('The provided value "%s" is not a numeric value.', $number));
+ }
+ }
+}