Passed
Branch master (73cfe8)
by Ondřej
06:41
created
showcase/issues/mutability.php 1 patch
Indentation   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -14,14 +14,14 @@
 block discarded – undo
14 14
 // VERSION 1: mutable relations
15 15
 
16 16
 foreach ($personRel as $row) {
17
-	print_r($row); // prints just the 'id', 'firstname', and 'lastname' attributes
17
+    print_r($row); // prints just the 'id', 'firstname', and 'lastname' attributes
18 18
 }
19 19
 
20 20
 
21 21
 // VERSION 2: immutable relations
22 22
 
23 23
 foreach ($personRel as $row) {
24
-	print_r($row); // prints the whole person row, not just the three attributes
24
+    print_r($row); // prints the whole person row, not just the three attributes
25 25
 }
26 26
 
27 27
 // VERSION 2a: the project() and similar methods copy the entire object into a new one
Please login to merge, or discard this patch.
showcase/issues/aliasing.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -15,12 +15,12 @@  discard block
 block discarded – undo
15 15
 $availPersonRel = new DbViewRelation('vw_avail_person', 'datatemplate');
16 16
 
17 17
 $sql = new StatementRelation(
18
-	"SELECT avail.room_id, %ln@p, 'the % sign is not expanded inside string literals'
18
+    "SELECT avail.room_id, %ln@p, 'the % sign is not expanded inside string literals'
19 19
 	 FROM % avail
20 20
 	      JOIN % p ON p.id = avail.person_id",
21
-	$personRel->project('id', 'firstname', 'lastname'),
22
-	$availPersonRel,
23
-	$personRel
21
+    $personRel->project('id', 'firstname', 'lastname'),
22
+    $availPersonRel,
23
+    $personRel
24 24
 );
25 25
 // "%ln" denotes a list of identifiers; in this case, it is fed with a relation, which causes to take all its attributes
26 26
 // the "@" sign denotes aliasing the relation of attributes
@@ -33,11 +33,11 @@  discard block
 block discarded – undo
33 33
 $availPersonRel = new DbViewRelation('vw_avail_person', 'datatemplate');
34 34
 
35 35
 $sql = new StatementRelation(
36
-	"SELECT avail.room_id, %ln, 'the % sign is not expanded inside string literals'
36
+    "SELECT avail.room_id, %ln, 'the % sign is not expanded inside string literals'
37 37
 	 FROM %
38 38
 	      JOIN % ON p.id = avail.person_id",
39
-	$personRel->project('id', 'firstname', 'lastname'),
40
-	$availPersonRel->aliased('avail'),
41
-	$personRel
39
+    $personRel->project('id', 'firstname', 'lastname'),
40
+    $availPersonRel->aliased('avail'),
41
+    $personRel
42 42
 );
43 43
 // "%ln" denotes a list of identifiers; in this case, it is fed with a relation, which causes to take all its attributes
Please login to merge, or discard this patch.
showcase/basic.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -9,32 +9,32 @@
 block discarded – undo
9 9
 $joesRel = $personRel->findWhere('firstname = %', 'Joe'); // the percent sign for no conversion - take "string"
10 10
 
11 11
 foreach ($joesRel as $row) {
12
-	printf("%s %s\n", $row['firstname'], $row['lastname']);
12
+    printf("%s %s\n", $row['firstname'], $row['lastname']);
13 13
 }
14 14
 
15 15
 $joesName = $joesRel->project(['firstname', 'lastname', 'dateofbirth']); // project() limits the attributes to be fetched only to the listed ones
16 16
 foreach ($joesName as $i => $row) {
17
-	printf("%d: %s %s, born on %s\n",
18
-		$i, $row['firstname'], $row['lastname'], $row['dateofbirth']->format('Y-m-d')
19
-	);
17
+    printf("%d: %s %s, born on %s\n",
18
+        $i, $row['firstname'], $row['lastname'], $row['dateofbirth']->format('Y-m-d')
19
+    );
20 20
 }
21 21
 
22 22
 $joeRows = $joesRel->map('id'); // fetch all columns, map by id
23 23
 foreach ($joeRows as $id => $row) {
24
-	printf("person #%d:\n", $id);
25
-	foreach ($row as $field => $value) {
26
-		printf("  %s => %s\n", $field, $value);
27
-	}
24
+    printf("person #%d:\n", $id);
25
+    foreach ($row as $field => $value) {
26
+        printf("  %s => %s\n", $field, $value);
27
+    }
28 28
 }
29 29
 
30 30
 $joesLastnameMap = $joesRel->map('id', 'lastname'); // fetch a map of person ids to lastnames
31 31
 foreach ($joesLastnameMap as $id => $lastname) {
32
-	printf("%d: %s\n", $id, $lastname);
32
+    printf("%d: %s\n", $id, $lastname);
33 33
 }
34 34
 
35 35
 $joesRowMap = $joesRel->map('id', ['firstname', 'lastname', 'dateofbirth']); // fetch a map of person ids to rows containing the person firstname, lastname, and date of birth
36 36
 foreach ($joesRowMap as $id => $row) {
37
-	printf("%d: %s %s, born on %s\n",
38
-		$id, $row['firstname'], $row['lastname'], $row['dateofbirth']->format('Y-m-d')
39
-	);
37
+    printf("%d: %s %s, born on %s\n",
38
+        $id, $row['firstname'], $row['lastname'], $row['dateofbirth']->format('Y-m-d')
39
+    );
40 40
 }
Please login to merge, or discard this patch.
showcase/sql.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -12,53 +12,53 @@
 block discarded – undo
12 12
 $roomIds = [3, 4, 5];
13 13
 
14 14
 $sql = new StatementRelation(
15
-	"SELECT avail.room_id, %ln@p, 'the % sign is not expanded inside string literals'
15
+    "SELECT avail.room_id, %ln@p, 'the % sign is not expanded inside string literals'
16 16
 	 FROM % avail
17 17
 	      JOIN % p ON p.id = avail.person_id
18 18
 	 WHERE avail.room_id IN (%l) AND p.id IN (%l)
19 19
 	 ORDER BY avail.room_id, p.lastname, p.firstname",
20
-	$personRel->project('id', 'firstname', 'lastname'), // limit the person relation only to these attributes
21
-	$availPersonRel, // the real power comes in: any relation shall be applicable in the query
22
-	$personRel,
23
-	$roomIds, $employeeIds
20
+    $personRel->project('id', 'firstname', 'lastname'), // limit the person relation only to these attributes
21
+    $availPersonRel, // the real power comes in: any relation shall be applicable in the query
22
+    $personRel,
23
+    $roomIds, $employeeIds
24 24
 );
25 25
 // "%l" generally means a list of something; another formatting token may follow, specifying the list subtype
26 26
 // the list subtype may be omitted to use the identity conversion, just like for any argument
27 27
 // here, "%ln" denotes a list of identifiers; in this case, it is fed with a relation, which causes to take all its (three) attributes
28 28
 // the "@" sign denotes aliasing the relation of attributes
29 29
 foreach ($sql as $row) {
30
-	printf("%s %s available for room #%d\n", $row['firstname'], $row['lastname'], $row['room_id']);
30
+    printf("%s %s available for room #%d\n", $row['firstname'], $row['lastname'], $row['room_id']);
31 31
 }
32 32
 
33 33
 
34 34
 class PersonRole extends DbTableRelation
35 35
 {
36
-	public function __construct()
37
-	{
38
-		parent::__construct('person_role');
39
-	}
36
+    public function __construct()
37
+    {
38
+        parent::__construct('person_role');
39
+    }
40 40
 }
41 41
 
42 42
 class Role extends DbTableRelation
43 43
 {
44
-	const ADMIN = 1;
44
+    const ADMIN = 1;
45 45
 
46
-	public function __construct()
47
-	{
48
-		parent::__construct('role');
49
-	}
46
+    public function __construct()
47
+    {
48
+        parent::__construct('role');
49
+    }
50 50
 }
51 51
 
52 52
 $adminInfo = $personRel->project([
53
-	'id',
54
-	'fname' => 'firstname',
55
-	'lastname' => 'UPPER(lastname)',
56
-	'is_admin' => new Statement("EXISTS(SELECT 1 FROM %n WHERE role = %)", PersonRole::class, Role::ADMIN), // unfortunately, PHP implements ::class as mere string, not an object of a special class, thus, the explicit %n must be used
53
+    'id',
54
+    'fname' => 'firstname',
55
+    'lastname' => 'UPPER(lastname)',
56
+    'is_admin' => new Statement("EXISTS(SELECT 1 FROM %n WHERE role = %)", PersonRole::class, Role::ADMIN), // unfortunately, PHP implements ::class as mere string, not an object of a special class, thus, the explicit %n must be used
57 57
 ]);
58 58
 foreach ($adminInfo as $row) {
59
-	echo $row['fname'] . ' ' . $row['lastname'];
60
-	if ($row['is_admin']) {
61
-		echo ' is admin';
62
-	}
63
-	echo "\n";
59
+    echo $row['fname'] . ' ' . $row['lastname'];
60
+    if ($row['is_admin']) {
61
+        echo ' is admin';
62
+    }
63
+    echo "\n";
64 64
 }
Please login to merge, or discard this patch.
showcase/exprs.php 1 patch
Indentation   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -5,13 +5,13 @@
 block discarded – undo
5 5
 use Ivory\Data\ValuesRelation;
6 6
 
7 7
 $valRel = new ValuesRelation([
8
-	[1, 'a', 3.14, true],
9
-	[5, 'g', 2.81, false],
8
+    [1, 'a', 3.14, true],
9
+    [5, 'g', 2.81, false],
10 10
 ]);
11 11
 $sel = new StatementRelation(
12
-	"SELECT * FROM % AS vals (num, letter, decim, flag) WHERE flag",
13
-	$valRel
12
+    "SELECT * FROM % AS vals (num, letter, decim, flag) WHERE flag",
13
+    $valRel
14 14
 );
15 15
 foreach ($sel as $row) {
16
-	printf("%d, %s, %f\n", $row['num'], $row['letter'], $row['decim']);
16
+    printf("%d, %s, %f\n", $row['num'], $row['letter'], $row['decim']);
17 17
 }
Please login to merge, or discard this patch.
showcase/processing.php 1 patch
Indentation   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -23,7 +23,7 @@  discard block
 block discarded – undo
23 23
  * Hence the lessonperson.schedulingstatus attribute, holding enum values "scheduled" or "actual".
24 24
  */
25 25
 $rel = new StatementRelation(
26
-	'SELECT lesson.id AS lesson_id, lesson.name AS lesson_name,
26
+    'SELECT lesson.id AS lesson_id, lesson.name AS lesson_name,
27 27
             lp.schedulingstatus,
28 28
 	        p.id AS person_id, p.firstname AS person_firstname, p.lastname AS person_lastname,
29 29
 	        p.schedabbr AS person_schedabbr
@@ -31,7 +31,7 @@  discard block
 block discarded – undo
31 31
 	      JOIN lessonperson lp ON lp.lesson_id = lesson.id
32 32
 	      JOIN person p ON p.id = lp.person_id
33 33
 	 WHERE lesson.actual_timerange && %',
34
-	new DateTimeRange(new \DateTime(), new \DateTime('+1 day'))
34
+    new DateTimeRange(new \DateTime(), new \DateTime('+1 day'))
35 35
 );
36 36
 
37 37
 // CASE 1; map: lesson ID => row
@@ -49,7 +49,7 @@  discard block
 block discarded – undo
49 49
 
50 50
 // CASE 4; map: lesson ID => map: person ID => user function result
51 51
 $res = $rel->map('lesson_id')->map('person_id')->col(function ($row) {
52
-	return ($row['person_schedabbr'] ? : mb_substr($row['person_lastname'], 0, 4));
52
+    return ($row['person_schedabbr'] ? : mb_substr($row['person_lastname'], 0, 4));
53 53
 });
54 54
 var_dump($res[4][7]); // prints, e.g., "Brez", which is the scheduling abbreviation of person 7, who teaches in lesson 4
55 55
 
Please login to merge, or discard this patch.
src/Ivory/Value/VarBitString.php 1 patch
Indentation   +56 added lines, -56 removed lines patch added patch discarded remove patch
@@ -17,66 +17,66 @@
 block discarded – undo
17 17
  */
18 18
 class VarBitString extends BitString
19 19
 {
20
-	/** @var int|null */
21
-	private $maxLength;
20
+    /** @var int|null */
21
+    private $maxLength;
22 22
 
23
-	/**
24
-	 * @param string $bits the bit string, i.e., a string of 1's and 0's
25
-	 * @param int|null $maxLength maximal length of the bit string in bits;
26
-	 *                            <tt>null</tt> for no limit;
27
-	 *                            if less than <tt>strlen($bits)</tt>, the bits get truncated on the right and a warning
28
-	 *                              is issued
29
-	 * @return VarBitString
30
-	 * @throws \InvalidArgumentException if <tt>$length</tt> is a non-positive number (PostgreSQL forbids it)
31
-	 */
32
-	public static function fromString($bits, $maxLength = null)
33
-	{
34
-		$bits = (string)$bits;
23
+    /**
24
+     * @param string $bits the bit string, i.e., a string of 1's and 0's
25
+     * @param int|null $maxLength maximal length of the bit string in bits;
26
+     *                            <tt>null</tt> for no limit;
27
+     *                            if less than <tt>strlen($bits)</tt>, the bits get truncated on the right and a warning
28
+     *                              is issued
29
+     * @return VarBitString
30
+     * @throws \InvalidArgumentException if <tt>$length</tt> is a non-positive number (PostgreSQL forbids it)
31
+     */
32
+    public static function fromString($bits, $maxLength = null)
33
+    {
34
+        $bits = (string)$bits;
35 35
 
36
-		if ($maxLength === null) {
37
-			return new VarBitString($bits, null);
38
-		}
39
-		elseif ($maxLength <= 0) {
40
-			throw new \InvalidArgumentException('maxLength is non-positive');
41
-		}
42
-		elseif ($maxLength < strlen($bits)) {
43
-			trigger_error("Bit string truncated to the length of $maxLength", E_USER_WARNING);
44
-			return new VarBitString(substr($bits, 0, $maxLength), $maxLength);
45
-		}
46
-		else {
47
-			return new VarBitString($bits, $maxLength);
48
-		}
49
-	}
36
+        if ($maxLength === null) {
37
+            return new VarBitString($bits, null);
38
+        }
39
+        elseif ($maxLength <= 0) {
40
+            throw new \InvalidArgumentException('maxLength is non-positive');
41
+        }
42
+        elseif ($maxLength < strlen($bits)) {
43
+            trigger_error("Bit string truncated to the length of $maxLength", E_USER_WARNING);
44
+            return new VarBitString(substr($bits, 0, $maxLength), $maxLength);
45
+        }
46
+        else {
47
+            return new VarBitString($bits, $maxLength);
48
+        }
49
+    }
50 50
 
51
-	/**
52
-	 * @param string $bits
53
-	 * @param int|null $maxLength
54
-	 */
55
-	protected function __construct($bits, $maxLength)
56
-	{
57
-		parent::__construct($bits);
51
+    /**
52
+     * @param string $bits
53
+     * @param int|null $maxLength
54
+     */
55
+    protected function __construct($bits, $maxLength)
56
+    {
57
+        parent::__construct($bits);
58 58
 
59
-		if ($maxLength !== null && $maxLength < 0) { // NOTE: zero max. length permitted for substr() to calculate correctly
60
-			throw new \InvalidArgumentException('maxLength is negative');
61
-		}
62
-		$this->maxLength = $maxLength;
63
-	}
59
+        if ($maxLength !== null && $maxLength < 0) { // NOTE: zero max. length permitted for substr() to calculate correctly
60
+            throw new \InvalidArgumentException('maxLength is negative');
61
+        }
62
+        $this->maxLength = $maxLength;
63
+    }
64 64
 
65
-	public function equals($other)
66
-	{
67
-		$parRes = parent::equals($other);
68
-		if (!$parRes) {
69
-			return $parRes;
70
-		}
71
-		/** @var VarBitString $other */
72
-		return ($this->maxLength == $other->maxLength);
73
-	}
65
+    public function equals($other)
66
+    {
67
+        $parRes = parent::equals($other);
68
+        if (!$parRes) {
69
+            return $parRes;
70
+        }
71
+        /** @var VarBitString $other */
72
+        return ($this->maxLength == $other->maxLength);
73
+    }
74 74
 
75
-	/**
76
-	 * @return int|null maximal length this bit string might have carried, or <tt>null</tt> for unlimited length
77
-	 */
78
-	public function getMaxLength()
79
-	{
80
-		return $this->maxLength;
81
-	}
75
+    /**
76
+     * @return int|null maximal length this bit string might have carried, or <tt>null</tt> for unlimited length
77
+     */
78
+    public function getMaxLength()
79
+    {
80
+        return $this->maxLength;
81
+    }
82 82
 }
Please login to merge, or discard this patch.
src/Ivory/Value/Decimal.php 1 patch
Indentation   +681 added lines, -681 removed lines patch added patch discarded remove patch
@@ -32,686 +32,686 @@
 block discarded – undo
32 32
  */
33 33
 class Decimal implements IComparable
34 34
 {
35
-	/** Maximal number of decimal digits considered by PostgreSQL. */
36
-	const SCALE_LIMIT = 16383;
37
-	/**
38
-	 * Minimal number of significant digits for inexact calculations, like division or square root. It reflects the
39
-	 * behaviour of PostgreSQL.
40
-	 */
41
-	const MIN_SIG_DIGITS = 16;
42
-
43
-	/** @var string|null string of decimal digits, including the sign and decimal point; null for the not-a-number */
44
-	private $val;
45
-	/** @var int number of decimal digits in the fractional part, to the right of the decimal point (if any) */
46
-	private $scale;
47
-
48
-	/**
49
-	 * @param string|int|float|Decimal|object $decimalNumber the value of the decimal number;
50
-	 *                                                       leading zeros are ignored (even after the minus sign), as
51
-	 *                                                         well as leading and trailing whitespace;
52
-	 *                                                       if an object is passed, it gets cast to string (which works
53
-	 *                                                         for {@link \GMP} objects, besides others)
54
-	 * @param int|null $scale the requested scale (i.e., the number of decimal digits in the fractional part) of the
55
-	 *                          number;
56
-	 *                        must be non-negative, or null;
57
-	 *                        if smaller than the scale of the given number, the number gets mathematically rounded to
58
-	 *                          the requested scale;
59
-	 *                        if greater than the scale of the given number, the number gets padded to have such many
60
-	 *                          decimal places;
61
-	 *                        if not given, it is computed from the given number automatically
62
-	 * @return Decimal
63
-	 */
64
-	public static function fromNumber($decimalNumber, $scale = null)
65
-	{
66
-		if ($decimalNumber === null) {
67
-			throw new \InvalidArgumentException('decimalNumber');
68
-		}
69
-		if ($scale < 0) {
70
-			throw new \InvalidArgumentException('scale');
71
-		}
72
-		if ($decimalNumber instanceof Decimal) {
73
-			if ($scale === null || $scale == $decimalNumber->scale) {
74
-				return $decimalNumber;
75
-			}
76
-			else {
77
-				return $decimalNumber->round($scale);
78
-			}
79
-		}
80
-
81
-		if (is_int($decimalNumber)) {
82
-			$val = (string)$decimalNumber;
83
-		}
84
-		elseif (is_float($decimalNumber)) {
85
-			$val = (string)$decimalNumber;
86
-			$ePos = stripos($val, 'e');
87
-			if ($ePos > 0) {
88
-				$exp = (int)substr($val, $ePos + 1);
89
-				$decs = substr($val, 0, $ePos);
90
-				list($wp, $dp) = explode('.', $decs, 2);
91
-
92
-				if ($exp >= 0) {
93
-					$dpLen = strlen($dp);
94
-					if ($exp >= $dpLen) {
95
-						$val = $wp . $dp . str_repeat('0', $exp - $dpLen);
96
-					}
97
-					else {
98
-						$val = $wp . substr($dp, 0, $exp) . '.' . substr($dp, $exp);
99
-					}
100
-				}
101
-				else {
102
-					$mn = ($wp[0] == '-' ? 1 : 0);
103
-					$ord = strlen($wp) - $mn;
104
-					if (-$exp >= $ord) {
105
-						$prefix = ($mn ? '-' : '') . '0.' . str_repeat('0', -$exp - $ord);
106
-						if (($mn ? ($wp != '-0') : ($wp != '0'))) {
107
-							$prefix .= substr($wp, $mn);
108
-						}
109
-						$val = $prefix . $dp;
110
-					}
111
-					else {
112
-						$val = substr($wp, 0, $mn + $ord + $exp) . '.' . substr($wp, $mn + $ord + $exp) . $dp;
113
-					}
114
-				}
115
-			}
116
-		}
117
-		else {
118
-			if (!preg_match('~^\s*(-?)(?|(\.[0-9]+)|0*([0-9]+(?:\.[0-9]*)?))\s*$~', (string)$decimalNumber, $m)) {
119
-				throw new \InvalidArgumentException('decimalNumber');
120
-			}
121
-			$val = $m[1] . $m[2]; // cuts off unnecessary leading zeros
122
-		}
123
-
124
-		return new Decimal($val, $scale);
125
-	}
126
-
127
-	/**
128
-	 * Gets the not-a-number special value. Returns the same object every time.
129
-	 *
130
-	 * It only equals to the not-a-number, and is greater than any number value. The result of any operation is, again,
131
-	 * the not-a-number.
132
-	 *
133
-	 * @return Decimal the not-a-number value
134
-	 */
135
-	public static function NaN()
136
-	{
137
-		static $dec = null;
138
-		if ($dec === null) {
139
-			$dec = new Decimal(null);
140
-		}
141
-		return $dec;
142
-	}
143
-
144
-	/**
145
-	 * Gets the decimal object representing the number zero. Returns the same object every time.
146
-	 *
147
-	 * @return Decimal
148
-	 */
149
-	public static function zero()
150
-	{
151
-		static $dec = null;
152
-		if ($dec === null) {
153
-			$dec = new Decimal(0);
154
-		}
155
-		return $dec;
156
-	}
157
-
158
-	/**
159
-	 * NOTE: The constructor is kept private so that extra sanity checks are performed on user input, while trusting the
160
-	 *       results of the operations without these extra checks.
161
-	 *
162
-	 * @param string|null $val
163
-	 * @param int|null $scale
164
-	 */
165
-	private function __construct($val, $scale = null)
166
-	{
167
-		if ($val === null) {
168
-			$this->val = null;
169
-			$this->scale = null;
170
-			return;
171
-		}
172
-
173
-		$this->val = (string)$val;
174
-
175
-		$decPoint = strpos($this->val, '.');
176
-		if ($scale < 0) {
177
-			$this->scale = 0;
178
-			$minus = ($this->val[0] == '-' ? 1 : 0);
179
-			if ($decPoint !== false) {
180
-				$digits = substr($this->val, $minus, $decPoint - $minus);
181
-			}
182
-			else {
183
-				$digits = ($minus ? substr($this->val, $minus) : $this->val);
184
-			}
185
-			$len = strlen($digits);
186
-			$sigPos = $len + $scale;
187
-			if ($sigPos < 0) {
188
-				$this->val = '0';
189
-				return;
190
-			}
191
-			$inc = ($digits[$sigPos] >= 5);
192
-			if (!$inc && $sigPos == 0) {
193
-				$this->val = '0';
194
-				return;
195
-			}
196
-			for ($i = $sigPos; $i < $len; $i++) {
197
-				$digits[$i] = '0';
198
-			}
199
-			if ($inc) {
200
-				$augend = '1' . str_repeat('0', -$scale);
201
-				$digits = bcadd($digits, $augend, 0);
202
-			}
203
-			$this->val = ($minus ? '-' : '') . $digits;
204
-		}
205
-		elseif ($decPoint === false) {
206
-			if ($scale > 0) {
207
-				$this->val .= '.' . str_repeat('0', $scale);
208
-				$this->scale = $scale;
209
-			}
210
-			else {
211
-				$this->scale = 0;
212
-			}
213
-		}
214
-		else {
215
-			$len = strlen($this->val);
216
-			$this->scale = $len - $decPoint - 1;
217
-			$scale = min(self::SCALE_LIMIT, ($scale === null ? $this->scale : max(0, $scale)));
218
-			if ($this->scale < $scale) {
219
-				$this->val .= str_repeat('0', $scale - $this->scale);
220
-				$this->scale = $scale;
221
-			}
222
-			elseif ($this->scale > $scale) {
223
-				$newLen = $len - ($this->scale - $scale);
224
-				$inc = ($this->val[$newLen] >= 5);
225
-				if ($scale == 0) {
226
-					$newLen--;
227
-				}
228
-				$this->val = substr($this->val, 0, $newLen);
229
-				if ($inc) {
230
-					$mn = ($this->val[0] == '-' ? '-' : '');
231
-					$augend = $mn . ($scale == 0 ? '1' : '.' . str_repeat('0', $scale - 1) . '1');
232
-					$this->val = bcadd($this->val, $augend, $scale);
233
-				}
234
-				$this->scale = $scale;
235
-			}
236
-
237
-			if ($this->val[0] == '.') {
238
-				$this->val = '0' . $this->val;
239
-			}
240
-			elseif ($this->val[0] == '-' && $this->val[1] == '.') {
241
-				$this->val[0] = '0';
242
-				$this->val = '-' . $this->val;
243
-			}
244
-		}
245
-
246
-		// eliminate negative zero - PostgreSQL does not differentiate between zeros
247
-		if ($this->val[0] == '-' && strspn($this->val, '-0.') == strlen($this->val)) {
248
-			$this->val = substr($this->val, 1);
249
-		}
250
-	}
251
-
252
-
253
-	/**
254
-	 * @return int|null number of decimal digits in the fractional part, to the right of the decimal point (if any), or
255
-	 *                  <tt>null</tt> for the not-a-number
256
-	 */
257
-	public function getScale()
258
-	{
259
-		return $this->scale;
260
-	}
261
-
262
-	/**
263
-	 * Compare this number numerically with another number.
264
-	 *
265
-	 * Note that using the `==` operator checks that the two {@link Decimal} objects are of the same value and scale,
266
-	 * which might or might not be desired. Such a difference only arises in the trailing fractional zero digits,
267
-	 * though.
268
-	 *
269
-	 * @param string|int|float|Decimal|object $number number to compare this number with
270
-	 * @return bool whether this number numerically equals to <tt>$number</tt>
271
-	 */
272
-	public function equals($number)
273
-	{
274
-		if ($number === null) {
275
-			return null;
276
-		}
277
-		return ($this->compareTo($number) == 0);
278
-	}
279
-
280
-	/**
281
-	 * @param string|int|float|Decimal|object $number
282
-	 * @return bool <tt>true</tt> iff this number is numerically less than <tt>$number</tt>
283
-	 */
284
-	public function lessThan($number)
285
-	{
286
-		return ($this->compareTo($number) < 0);
287
-	}
288
-
289
-	/**
290
-	 * @param string|int|float|Decimal|object $number
291
-	 * @return bool <tt>true</tt> iff this number is numerically greater than <tt>$number</tt>
292
-	 */
293
-	public function greaterThan($number)
294
-	{
295
-		return ($this->compareTo($number) > 0);
296
-	}
297
-
298
-	/**
299
-	 * @param string|int|float|Decimal|object $number number to compare this number with
300
-	 * @return int -1, 0, or 1 if this number is numerically less than, equal to, or greater than <tt>$number</tt>
301
-	 */
302
-	public function compareTo($number)
303
-	{
304
-		$arg = self::fromNumber($number);
305
-		if ($this->isNaN()) {
306
-			if ($arg->isNaN()) {
307
-				return 0;
308
-			}
309
-			else {
310
-				return 1;
311
-			}
312
-		}
313
-		elseif ($arg->isNaN()) {
314
-			return -1;
315
-		}
316
-		else {
317
-			return bccomp($this->val, $arg->val, max($this->scale, $arg->scale));
318
-		}
319
-	}
320
-
321
-	/**
322
-	 * @return bool whether this is a not-a-number
323
-	 */
324
-	public function isNaN()
325
-	{
326
-		return ($this->val === null);
327
-	}
328
-
329
-	/**
330
-	 * @return bool whether this is zero
331
-	 */
332
-	public function isZero()
333
-	{
334
-		return ($this->equals(self::zero()));
335
-	}
336
-
337
-	/**
338
-	 * @return bool whether this is a positive number
339
-	 */
340
-	public function isPositive()
341
-	{
342
-		return (!$this->isNaN() && $this->compareTo(self::zero()) > 0);
343
-	}
344
-
345
-	/**
346
-	 * @return bool whether this is a negative number
347
-	 */
348
-	public function isNegative()
349
-	{
350
-		return (!$this->isNaN() && $this->val[0] == '-');
351
-	}
352
-
353
-	/**
354
-	 * @return bool whether this is an integer, i.e., a number without decimal part (or the decimal part equal to zero)
355
-	 */
356
-	public function isInteger()
357
-	{
358
-		if ($this->isNaN()) {
359
-			return false;
360
-		}
361
-		if (strpos($this->val, '.') === false) {
362
-			return true;
363
-		}
364
-		for ($i = strlen($this->val) - 1; ; $i--) {
365
-			switch ($this->val[$i]) {
366
-				case '.':
367
-					return true;
368
-				case '0':
369
-					break;
370
-				default:
371
-					return false;
372
-			}
373
-		}
374
-		return true;
375
-	}
376
-
377
-	/**
378
-	 * @param string|int|float|Decimal|object $augend
379
-	 * @return Decimal a new decimal number, representing the result of sum of this and the given number
380
-	 */
381
-	public function add($augend)
382
-	{
383
-		if ($this->isNaN()) {
384
-			return $this;
385
-		}
386
-		$arg = self::fromNumber($augend);
387
-		if ($arg->isNaN()) {
388
-			return $arg;
389
-		}
390
-		$scale = max($this->scale, $arg->scale);
391
-		return new Decimal(bcadd($this->val, $arg->val, $scale + 1), $scale);
392
-	}
393
-
394
-	/**
395
-	 * @param string|int|float|Decimal|object $subtrahend
396
-	 * @return Decimal a new decimal number, representing the result of subtraction of this and the given number
397
-	 */
398
-	public function subtract($subtrahend)
399
-	{
400
-		if ($this->isNaN()) {
401
-			return $this;
402
-		}
403
-		$arg = self::fromNumber($subtrahend);
404
-		if ($arg->isNaN()) {
405
-			return $arg;
406
-		}
407
-		$scale = max($this->scale, $arg->scale);
408
-		return new Decimal(bcsub($this->val, $arg->val, $scale + 1), $scale);
409
-	}
410
-
411
-	/**
412
-	 * @param string|int|float|Decimal|object $multiplicand
413
-	 * @return Decimal a new decimal number, representing the result of multiplication of this and the given number
414
-	 */
415
-	public function multiply($multiplicand)
416
-	{
417
-		if ($this->isNaN()) {
418
-			return $this;
419
-		}
420
-		$arg = self::fromNumber($multiplicand);
421
-		if ($arg->isNaN()) {
422
-			return $arg;
423
-		}
424
-		$scale = min($this->scale + $arg->scale, self::SCALE_LIMIT);
425
-		return new Decimal(bcmul($this->val, $arg->val, $scale + 1), $scale);
426
-	}
427
-
428
-	/**
429
-	 * @param string|int|float|Decimal|object $divisor
430
-	 * @return Decimal a new decimal number, representing the result of division of this number with the given number
431
-	 * @throws \RuntimeException if <tt>$divisor</tt> is zero
432
-	 */
433
-	public function divide($divisor)
434
-	{
435
-		if ($this->isNaN()) {
436
-			return $this;
437
-		}
438
-		$arg = self::fromNumber($divisor);
439
-		if ($arg->isNaN()) {
440
-			return $arg;
441
-		}
442
-		$scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
443
-		return new Decimal(bcdiv($this->val, $arg->val, $scale + 1), $scale);
444
-	}
445
-
446
-	/**
447
-	 * @param string|int|float|Decimal|object $modulus
448
-	 * @return Decimal remainder of this number divided by <tt>$modulus</tt>
449
-	 * @throws \RuntimeException if <tt>$modulus</tt> is zero
450
-	 */
451
-	public function mod($modulus)
452
-	{
453
-		if ($this->isNaN()) {
454
-			return $this;
455
-		}
456
-		$arg = self::fromNumber($modulus);
457
-		if ($arg->isNaN()) {
458
-			return $arg;
459
-		}
460
-		if ($arg->isZero()) {
461
-			throw new \RuntimeException('Division by zero');
462
-		}
463
-
464
-		// NOTE: bcmod() only calculates integer modulus
465
-		$a = $this->abs();
466
-		$b = $arg->abs();
467
-		$m = $a->subtract($b->multiply($a->divide($b)->floor()));
468
-		if ($this->isNegative()) {
469
-			return $m->negate();
470
-		}
471
-		else {
472
-			return $m;
473
-		}
474
-	}
475
-
476
-	/**
477
-	 * @todo more precise calculation of fractional powers - now only double precision is used
478
-	 * @param string|int|float|Decimal|object $power
479
-	 * @return Decimal this number powered to <tt>$number</tt>
480
-	 * @throws \RuntimeException if this number is negative while <tt>$power</tt> is non-integer (that would lead to a
481
-	 *                             complex result)
482
-	 */
483
-	public function pow($power)
484
-	{
485
-		if ($this->isNaN()) {
486
-			return $this;
487
-		}
488
-		$arg = self::fromNumber($power);
489
-		if ($arg->isNaN()) {
490
-			return $arg;
491
-		}
492
-
493
-		$scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
494
-
495
-		// NOTE: bcpow() only takes integer powers
496
-		if ($arg->isInteger()) {
497
-			return new Decimal(bcpow($this->val, $arg->val, $scale + 1), $scale);
498
-		}
499
-		else {
500
-			if ($this->isNegative()) {
501
-				throw new \RuntimeException('Negative number raised to a non-integer power yields a complex result');
502
-			}
503
-			return self::fromNumber(pow($this->toFloat(), $arg->toFloat()));
504
-		}
505
-	}
506
-
507
-	/**
508
-	 * @return Decimal the absolute value of this number
509
-	 */
510
-	public function abs()
511
-	{
512
-		if ($this->isNegative()) {
513
-			return $this->negate();
514
-		}
515
-		else {
516
-			return $this;
517
-		}
518
-	}
519
-
520
-	/**
521
-	 * @return Decimal a new decimal number, representing the negative value of this number
522
-	 */
523
-	public function negate()
524
-	{
525
-		if ($this->isNaN() || $this->isZero()) {
526
-			return $this;
527
-		}
528
-		elseif ($this->val[0] == '-') {
529
-			return new Decimal(substr($this->val, 1));
530
-		}
531
-		else {
532
-			return new Decimal('-' . $this->val);
533
-		}
534
-	}
535
-
536
-	/**
537
-	 * Computes the exact factorial of this number.
538
-	 * Only defined on numbers of scale zero, i.e., those without decimal part, and on the not-a-number.
539
-	 *
540
-	 * In conformance with PostgreSQL, the factorial of non-positive numbers is defined to be 1.
541
-	 *
542
-	 * @return Decimal the factorial of this number
543
-	 * @throws UndefinedOperationException if this number is neither a zero-scale number nor the not-a-number
544
-	 */
545
-	public function factorial()
546
-	{
547
-		if ($this->isNaN()) {
548
-			return $this;
549
-		}
550
-		if ($this->scale > 0) {
551
-			throw new UndefinedOperationException('Number with a decimal part');
552
-		}
553
-		if ($this->lessThan(2)) {
554
-			return new Decimal(1);
555
-		}
556
-
557
-		if (System::hasGMP()) {
558
-			return new Decimal(gmp_strval(gmp_fact($this->toGMP())));
559
-		}
560
-
561
-		// OPT: there are more efficient algorithms calculating the factorial; see, e.g., http://www.luschny.de/math/factorial/index.html
562
-		$result = new Decimal(2);
563
-		for ($i = new Decimal(3); $i->compareTo($this) <= 0; $i = $i->add(1)) {
564
-			$result = $result->multiply($i);
565
-		}
566
-		return $result;
567
-	}
568
-
569
-	/**
570
-	 * @param int $scale number of decimal places to round this number to; may be negative to round to higher orders
571
-	 * @return Decimal a new decimal number, representing this number rounded to <tt>$scale</tt> decimal places
572
-	 */
573
-	public function round($scale = 0)
574
-	{
575
-		return new Decimal($this->val, $scale);
576
-	}
577
-
578
-	/**
579
-	 * @return Decimal largest integer not greater than this number
580
-	 */
581
-	public function floor()
582
-	{
583
-		$decPoint = strpos($this->val, '.');
584
-		if ($decPoint === false) {
585
-			return $this;
586
-		}
587
-		if ($this->val[0] != '-') {
588
-			return new Decimal(substr($this->val, 0, $decPoint));
589
-		}
590
-		elseif ($this->isInteger()) {
591
-			return new Decimal($this->val, 0);
592
-		}
593
-		else {
594
-			// negative number with non-zero fractional part
595
-			return new Decimal(bcsub(substr($this->val, 0, $decPoint), 1, 0));
596
-		}
597
-	}
598
-
599
-	/**
600
-	 * @return Decimal smallest integer not less than this number
601
-	 */
602
-	public function ceil()
603
-	{
604
-		$decPoint = strpos($this->val, '.');
605
-		if ($decPoint === false) {
606
-			return $this;
607
-		}
608
-		if ($this->val[0] == '-') {
609
-			return new Decimal(substr($this->val, 0, $decPoint));
610
-		}
611
-		elseif ($this->isInteger()) {
612
-			return new Decimal($this->val, 0);
613
-		}
614
-		else {
615
-			// non-negative number with non-zero fractional part
616
-			return new Decimal(bcadd(substr($this->val, 0, $decPoint), 1, 0));
617
-		}
618
-	}
619
-
620
-	/**
621
-	 * @param string|int|float|Decimal|object $number
622
-	 * @return Decimal the greater of this number and <tt>$number</tt>, preferably this number if numerically equal
623
-	 */
624
-	public function max($number)
625
-	{
626
-		$arg = self::fromNumber($number);
627
-		if ($this->lessThan($arg)) {
628
-			return $arg;
629
-		}
630
-		else {
631
-			return $this;
632
-		}
633
-	}
634
-
635
-	/**
636
-	 * @param string|int|float|Decimal|object $number
637
-	 * @return Decimal the smaller of this number and <tt>$number</tt>, preferably this number if numerically equal
638
-	 */
639
-	public function min($number)
640
-	{
641
-		$arg = self::fromNumber($number);
642
-		if ($this->greaterThan($arg)) {
643
-			return $arg;
644
-		}
645
-		else {
646
-			return $this;
647
-		}
648
-	}
649
-
650
-	/**
651
-	 * @return Decimal a new decimal number, representing the square root of this number
652
-	 * @throws UndefinedOperationException if this is a negative number
653
-	 */
654
-	public function sqrt()
655
-	{
656
-		if ($this->isNaN()) {
657
-			return $this;
658
-		}
659
-		if ($this->isNegative()) {
660
-			throw new UndefinedOperationException('square root of negative number');
661
-		}
662
-		$scale = max($this->scale, self::MIN_SIG_DIGITS);
663
-		return new Decimal(bcsqrt($this->val, $scale + 1));
664
-	}
665
-
666
-
667
-	/**
668
-	 * @return int|null the value of this number cast explicitly to <tt>int</tt>;
669
-	 *                  for big numbers, this yields the maximal available integer value (i.e., <tt>PHP_INT_MAX</tt> or
670
-	 *                    <tt>PHP_INT_MIN</tt>);
35
+    /** Maximal number of decimal digits considered by PostgreSQL. */
36
+    const SCALE_LIMIT = 16383;
37
+    /**
38
+     * Minimal number of significant digits for inexact calculations, like division or square root. It reflects the
39
+     * behaviour of PostgreSQL.
40
+     */
41
+    const MIN_SIG_DIGITS = 16;
42
+
43
+    /** @var string|null string of decimal digits, including the sign and decimal point; null for the not-a-number */
44
+    private $val;
45
+    /** @var int number of decimal digits in the fractional part, to the right of the decimal point (if any) */
46
+    private $scale;
47
+
48
+    /**
49
+     * @param string|int|float|Decimal|object $decimalNumber the value of the decimal number;
50
+     *                                                       leading zeros are ignored (even after the minus sign), as
51
+     *                                                         well as leading and trailing whitespace;
52
+     *                                                       if an object is passed, it gets cast to string (which works
53
+     *                                                         for {@link \GMP} objects, besides others)
54
+     * @param int|null $scale the requested scale (i.e., the number of decimal digits in the fractional part) of the
55
+     *                          number;
56
+     *                        must be non-negative, or null;
57
+     *                        if smaller than the scale of the given number, the number gets mathematically rounded to
58
+     *                          the requested scale;
59
+     *                        if greater than the scale of the given number, the number gets padded to have such many
60
+     *                          decimal places;
61
+     *                        if not given, it is computed from the given number automatically
62
+     * @return Decimal
63
+     */
64
+    public static function fromNumber($decimalNumber, $scale = null)
65
+    {
66
+        if ($decimalNumber === null) {
67
+            throw new \InvalidArgumentException('decimalNumber');
68
+        }
69
+        if ($scale < 0) {
70
+            throw new \InvalidArgumentException('scale');
71
+        }
72
+        if ($decimalNumber instanceof Decimal) {
73
+            if ($scale === null || $scale == $decimalNumber->scale) {
74
+                return $decimalNumber;
75
+            }
76
+            else {
77
+                return $decimalNumber->round($scale);
78
+            }
79
+        }
80
+
81
+        if (is_int($decimalNumber)) {
82
+            $val = (string)$decimalNumber;
83
+        }
84
+        elseif (is_float($decimalNumber)) {
85
+            $val = (string)$decimalNumber;
86
+            $ePos = stripos($val, 'e');
87
+            if ($ePos > 0) {
88
+                $exp = (int)substr($val, $ePos + 1);
89
+                $decs = substr($val, 0, $ePos);
90
+                list($wp, $dp) = explode('.', $decs, 2);
91
+
92
+                if ($exp >= 0) {
93
+                    $dpLen = strlen($dp);
94
+                    if ($exp >= $dpLen) {
95
+                        $val = $wp . $dp . str_repeat('0', $exp - $dpLen);
96
+                    }
97
+                    else {
98
+                        $val = $wp . substr($dp, 0, $exp) . '.' . substr($dp, $exp);
99
+                    }
100
+                }
101
+                else {
102
+                    $mn = ($wp[0] == '-' ? 1 : 0);
103
+                    $ord = strlen($wp) - $mn;
104
+                    if (-$exp >= $ord) {
105
+                        $prefix = ($mn ? '-' : '') . '0.' . str_repeat('0', -$exp - $ord);
106
+                        if (($mn ? ($wp != '-0') : ($wp != '0'))) {
107
+                            $prefix .= substr($wp, $mn);
108
+                        }
109
+                        $val = $prefix . $dp;
110
+                    }
111
+                    else {
112
+                        $val = substr($wp, 0, $mn + $ord + $exp) . '.' . substr($wp, $mn + $ord + $exp) . $dp;
113
+                    }
114
+                }
115
+            }
116
+        }
117
+        else {
118
+            if (!preg_match('~^\s*(-?)(?|(\.[0-9]+)|0*([0-9]+(?:\.[0-9]*)?))\s*$~', (string)$decimalNumber, $m)) {
119
+                throw new \InvalidArgumentException('decimalNumber');
120
+            }
121
+            $val = $m[1] . $m[2]; // cuts off unnecessary leading zeros
122
+        }
123
+
124
+        return new Decimal($val, $scale);
125
+    }
126
+
127
+    /**
128
+     * Gets the not-a-number special value. Returns the same object every time.
129
+     *
130
+     * It only equals to the not-a-number, and is greater than any number value. The result of any operation is, again,
131
+     * the not-a-number.
132
+     *
133
+     * @return Decimal the not-a-number value
134
+     */
135
+    public static function NaN()
136
+    {
137
+        static $dec = null;
138
+        if ($dec === null) {
139
+            $dec = new Decimal(null);
140
+        }
141
+        return $dec;
142
+    }
143
+
144
+    /**
145
+     * Gets the decimal object representing the number zero. Returns the same object every time.
146
+     *
147
+     * @return Decimal
148
+     */
149
+    public static function zero()
150
+    {
151
+        static $dec = null;
152
+        if ($dec === null) {
153
+            $dec = new Decimal(0);
154
+        }
155
+        return $dec;
156
+    }
157
+
158
+    /**
159
+     * NOTE: The constructor is kept private so that extra sanity checks are performed on user input, while trusting the
160
+     *       results of the operations without these extra checks.
161
+     *
162
+     * @param string|null $val
163
+     * @param int|null $scale
164
+     */
165
+    private function __construct($val, $scale = null)
166
+    {
167
+        if ($val === null) {
168
+            $this->val = null;
169
+            $this->scale = null;
170
+            return;
171
+        }
172
+
173
+        $this->val = (string)$val;
174
+
175
+        $decPoint = strpos($this->val, '.');
176
+        if ($scale < 0) {
177
+            $this->scale = 0;
178
+            $minus = ($this->val[0] == '-' ? 1 : 0);
179
+            if ($decPoint !== false) {
180
+                $digits = substr($this->val, $minus, $decPoint - $minus);
181
+            }
182
+            else {
183
+                $digits = ($minus ? substr($this->val, $minus) : $this->val);
184
+            }
185
+            $len = strlen($digits);
186
+            $sigPos = $len + $scale;
187
+            if ($sigPos < 0) {
188
+                $this->val = '0';
189
+                return;
190
+            }
191
+            $inc = ($digits[$sigPos] >= 5);
192
+            if (!$inc && $sigPos == 0) {
193
+                $this->val = '0';
194
+                return;
195
+            }
196
+            for ($i = $sigPos; $i < $len; $i++) {
197
+                $digits[$i] = '0';
198
+            }
199
+            if ($inc) {
200
+                $augend = '1' . str_repeat('0', -$scale);
201
+                $digits = bcadd($digits, $augend, 0);
202
+            }
203
+            $this->val = ($minus ? '-' : '') . $digits;
204
+        }
205
+        elseif ($decPoint === false) {
206
+            if ($scale > 0) {
207
+                $this->val .= '.' . str_repeat('0', $scale);
208
+                $this->scale = $scale;
209
+            }
210
+            else {
211
+                $this->scale = 0;
212
+            }
213
+        }
214
+        else {
215
+            $len = strlen($this->val);
216
+            $this->scale = $len - $decPoint - 1;
217
+            $scale = min(self::SCALE_LIMIT, ($scale === null ? $this->scale : max(0, $scale)));
218
+            if ($this->scale < $scale) {
219
+                $this->val .= str_repeat('0', $scale - $this->scale);
220
+                $this->scale = $scale;
221
+            }
222
+            elseif ($this->scale > $scale) {
223
+                $newLen = $len - ($this->scale - $scale);
224
+                $inc = ($this->val[$newLen] >= 5);
225
+                if ($scale == 0) {
226
+                    $newLen--;
227
+                }
228
+                $this->val = substr($this->val, 0, $newLen);
229
+                if ($inc) {
230
+                    $mn = ($this->val[0] == '-' ? '-' : '');
231
+                    $augend = $mn . ($scale == 0 ? '1' : '.' . str_repeat('0', $scale - 1) . '1');
232
+                    $this->val = bcadd($this->val, $augend, $scale);
233
+                }
234
+                $this->scale = $scale;
235
+            }
236
+
237
+            if ($this->val[0] == '.') {
238
+                $this->val = '0' . $this->val;
239
+            }
240
+            elseif ($this->val[0] == '-' && $this->val[1] == '.') {
241
+                $this->val[0] = '0';
242
+                $this->val = '-' . $this->val;
243
+            }
244
+        }
245
+
246
+        // eliminate negative zero - PostgreSQL does not differentiate between zeros
247
+        if ($this->val[0] == '-' && strspn($this->val, '-0.') == strlen($this->val)) {
248
+            $this->val = substr($this->val, 1);
249
+        }
250
+    }
251
+
252
+
253
+    /**
254
+     * @return int|null number of decimal digits in the fractional part, to the right of the decimal point (if any), or
255
+     *                  <tt>null</tt> for the not-a-number
256
+     */
257
+    public function getScale()
258
+    {
259
+        return $this->scale;
260
+    }
261
+
262
+    /**
263
+     * Compare this number numerically with another number.
264
+     *
265
+     * Note that using the `==` operator checks that the two {@link Decimal} objects are of the same value and scale,
266
+     * which might or might not be desired. Such a difference only arises in the trailing fractional zero digits,
267
+     * though.
268
+     *
269
+     * @param string|int|float|Decimal|object $number number to compare this number with
270
+     * @return bool whether this number numerically equals to <tt>$number</tt>
271
+     */
272
+    public function equals($number)
273
+    {
274
+        if ($number === null) {
275
+            return null;
276
+        }
277
+        return ($this->compareTo($number) == 0);
278
+    }
279
+
280
+    /**
281
+     * @param string|int|float|Decimal|object $number
282
+     * @return bool <tt>true</tt> iff this number is numerically less than <tt>$number</tt>
283
+     */
284
+    public function lessThan($number)
285
+    {
286
+        return ($this->compareTo($number) < 0);
287
+    }
288
+
289
+    /**
290
+     * @param string|int|float|Decimal|object $number
291
+     * @return bool <tt>true</tt> iff this number is numerically greater than <tt>$number</tt>
292
+     */
293
+    public function greaterThan($number)
294
+    {
295
+        return ($this->compareTo($number) > 0);
296
+    }
297
+
298
+    /**
299
+     * @param string|int|float|Decimal|object $number number to compare this number with
300
+     * @return int -1, 0, or 1 if this number is numerically less than, equal to, or greater than <tt>$number</tt>
301
+     */
302
+    public function compareTo($number)
303
+    {
304
+        $arg = self::fromNumber($number);
305
+        if ($this->isNaN()) {
306
+            if ($arg->isNaN()) {
307
+                return 0;
308
+            }
309
+            else {
310
+                return 1;
311
+            }
312
+        }
313
+        elseif ($arg->isNaN()) {
314
+            return -1;
315
+        }
316
+        else {
317
+            return bccomp($this->val, $arg->val, max($this->scale, $arg->scale));
318
+        }
319
+    }
320
+
321
+    /**
322
+     * @return bool whether this is a not-a-number
323
+     */
324
+    public function isNaN()
325
+    {
326
+        return ($this->val === null);
327
+    }
328
+
329
+    /**
330
+     * @return bool whether this is zero
331
+     */
332
+    public function isZero()
333
+    {
334
+        return ($this->equals(self::zero()));
335
+    }
336
+
337
+    /**
338
+     * @return bool whether this is a positive number
339
+     */
340
+    public function isPositive()
341
+    {
342
+        return (!$this->isNaN() && $this->compareTo(self::zero()) > 0);
343
+    }
344
+
345
+    /**
346
+     * @return bool whether this is a negative number
347
+     */
348
+    public function isNegative()
349
+    {
350
+        return (!$this->isNaN() && $this->val[0] == '-');
351
+    }
352
+
353
+    /**
354
+     * @return bool whether this is an integer, i.e., a number without decimal part (or the decimal part equal to zero)
355
+     */
356
+    public function isInteger()
357
+    {
358
+        if ($this->isNaN()) {
359
+            return false;
360
+        }
361
+        if (strpos($this->val, '.') === false) {
362
+            return true;
363
+        }
364
+        for ($i = strlen($this->val) - 1; ; $i--) {
365
+            switch ($this->val[$i]) {
366
+                case '.':
367
+                    return true;
368
+                case '0':
369
+                    break;
370
+                default:
371
+                    return false;
372
+            }
373
+        }
374
+        return true;
375
+    }
376
+
377
+    /**
378
+     * @param string|int|float|Decimal|object $augend
379
+     * @return Decimal a new decimal number, representing the result of sum of this and the given number
380
+     */
381
+    public function add($augend)
382
+    {
383
+        if ($this->isNaN()) {
384
+            return $this;
385
+        }
386
+        $arg = self::fromNumber($augend);
387
+        if ($arg->isNaN()) {
388
+            return $arg;
389
+        }
390
+        $scale = max($this->scale, $arg->scale);
391
+        return new Decimal(bcadd($this->val, $arg->val, $scale + 1), $scale);
392
+    }
393
+
394
+    /**
395
+     * @param string|int|float|Decimal|object $subtrahend
396
+     * @return Decimal a new decimal number, representing the result of subtraction of this and the given number
397
+     */
398
+    public function subtract($subtrahend)
399
+    {
400
+        if ($this->isNaN()) {
401
+            return $this;
402
+        }
403
+        $arg = self::fromNumber($subtrahend);
404
+        if ($arg->isNaN()) {
405
+            return $arg;
406
+        }
407
+        $scale = max($this->scale, $arg->scale);
408
+        return new Decimal(bcsub($this->val, $arg->val, $scale + 1), $scale);
409
+    }
410
+
411
+    /**
412
+     * @param string|int|float|Decimal|object $multiplicand
413
+     * @return Decimal a new decimal number, representing the result of multiplication of this and the given number
414
+     */
415
+    public function multiply($multiplicand)
416
+    {
417
+        if ($this->isNaN()) {
418
+            return $this;
419
+        }
420
+        $arg = self::fromNumber($multiplicand);
421
+        if ($arg->isNaN()) {
422
+            return $arg;
423
+        }
424
+        $scale = min($this->scale + $arg->scale, self::SCALE_LIMIT);
425
+        return new Decimal(bcmul($this->val, $arg->val, $scale + 1), $scale);
426
+    }
427
+
428
+    /**
429
+     * @param string|int|float|Decimal|object $divisor
430
+     * @return Decimal a new decimal number, representing the result of division of this number with the given number
431
+     * @throws \RuntimeException if <tt>$divisor</tt> is zero
432
+     */
433
+    public function divide($divisor)
434
+    {
435
+        if ($this->isNaN()) {
436
+            return $this;
437
+        }
438
+        $arg = self::fromNumber($divisor);
439
+        if ($arg->isNaN()) {
440
+            return $arg;
441
+        }
442
+        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
443
+        return new Decimal(bcdiv($this->val, $arg->val, $scale + 1), $scale);
444
+    }
445
+
446
+    /**
447
+     * @param string|int|float|Decimal|object $modulus
448
+     * @return Decimal remainder of this number divided by <tt>$modulus</tt>
449
+     * @throws \RuntimeException if <tt>$modulus</tt> is zero
450
+     */
451
+    public function mod($modulus)
452
+    {
453
+        if ($this->isNaN()) {
454
+            return $this;
455
+        }
456
+        $arg = self::fromNumber($modulus);
457
+        if ($arg->isNaN()) {
458
+            return $arg;
459
+        }
460
+        if ($arg->isZero()) {
461
+            throw new \RuntimeException('Division by zero');
462
+        }
463
+
464
+        // NOTE: bcmod() only calculates integer modulus
465
+        $a = $this->abs();
466
+        $b = $arg->abs();
467
+        $m = $a->subtract($b->multiply($a->divide($b)->floor()));
468
+        if ($this->isNegative()) {
469
+            return $m->negate();
470
+        }
471
+        else {
472
+            return $m;
473
+        }
474
+    }
475
+
476
+    /**
477
+     * @todo more precise calculation of fractional powers - now only double precision is used
478
+     * @param string|int|float|Decimal|object $power
479
+     * @return Decimal this number powered to <tt>$number</tt>
480
+     * @throws \RuntimeException if this number is negative while <tt>$power</tt> is non-integer (that would lead to a
481
+     *                             complex result)
482
+     */
483
+    public function pow($power)
484
+    {
485
+        if ($this->isNaN()) {
486
+            return $this;
487
+        }
488
+        $arg = self::fromNumber($power);
489
+        if ($arg->isNaN()) {
490
+            return $arg;
491
+        }
492
+
493
+        $scale = max($this->scale, $arg->scale, self::MIN_SIG_DIGITS);
494
+
495
+        // NOTE: bcpow() only takes integer powers
496
+        if ($arg->isInteger()) {
497
+            return new Decimal(bcpow($this->val, $arg->val, $scale + 1), $scale);
498
+        }
499
+        else {
500
+            if ($this->isNegative()) {
501
+                throw new \RuntimeException('Negative number raised to a non-integer power yields a complex result');
502
+            }
503
+            return self::fromNumber(pow($this->toFloat(), $arg->toFloat()));
504
+        }
505
+    }
506
+
507
+    /**
508
+     * @return Decimal the absolute value of this number
509
+     */
510
+    public function abs()
511
+    {
512
+        if ($this->isNegative()) {
513
+            return $this->negate();
514
+        }
515
+        else {
516
+            return $this;
517
+        }
518
+    }
519
+
520
+    /**
521
+     * @return Decimal a new decimal number, representing the negative value of this number
522
+     */
523
+    public function negate()
524
+    {
525
+        if ($this->isNaN() || $this->isZero()) {
526
+            return $this;
527
+        }
528
+        elseif ($this->val[0] == '-') {
529
+            return new Decimal(substr($this->val, 1));
530
+        }
531
+        else {
532
+            return new Decimal('-' . $this->val);
533
+        }
534
+    }
535
+
536
+    /**
537
+     * Computes the exact factorial of this number.
538
+     * Only defined on numbers of scale zero, i.e., those without decimal part, and on the not-a-number.
539
+     *
540
+     * In conformance with PostgreSQL, the factorial of non-positive numbers is defined to be 1.
541
+     *
542
+     * @return Decimal the factorial of this number
543
+     * @throws UndefinedOperationException if this number is neither a zero-scale number nor the not-a-number
544
+     */
545
+    public function factorial()
546
+    {
547
+        if ($this->isNaN()) {
548
+            return $this;
549
+        }
550
+        if ($this->scale > 0) {
551
+            throw new UndefinedOperationException('Number with a decimal part');
552
+        }
553
+        if ($this->lessThan(2)) {
554
+            return new Decimal(1);
555
+        }
556
+
557
+        if (System::hasGMP()) {
558
+            return new Decimal(gmp_strval(gmp_fact($this->toGMP())));
559
+        }
560
+
561
+        // OPT: there are more efficient algorithms calculating the factorial; see, e.g., http://www.luschny.de/math/factorial/index.html
562
+        $result = new Decimal(2);
563
+        for ($i = new Decimal(3); $i->compareTo($this) <= 0; $i = $i->add(1)) {
564
+            $result = $result->multiply($i);
565
+        }
566
+        return $result;
567
+    }
568
+
569
+    /**
570
+     * @param int $scale number of decimal places to round this number to; may be negative to round to higher orders
571
+     * @return Decimal a new decimal number, representing this number rounded to <tt>$scale</tt> decimal places
572
+     */
573
+    public function round($scale = 0)
574
+    {
575
+        return new Decimal($this->val, $scale);
576
+    }
577
+
578
+    /**
579
+     * @return Decimal largest integer not greater than this number
580
+     */
581
+    public function floor()
582
+    {
583
+        $decPoint = strpos($this->val, '.');
584
+        if ($decPoint === false) {
585
+            return $this;
586
+        }
587
+        if ($this->val[0] != '-') {
588
+            return new Decimal(substr($this->val, 0, $decPoint));
589
+        }
590
+        elseif ($this->isInteger()) {
591
+            return new Decimal($this->val, 0);
592
+        }
593
+        else {
594
+            // negative number with non-zero fractional part
595
+            return new Decimal(bcsub(substr($this->val, 0, $decPoint), 1, 0));
596
+        }
597
+    }
598
+
599
+    /**
600
+     * @return Decimal smallest integer not less than this number
601
+     */
602
+    public function ceil()
603
+    {
604
+        $decPoint = strpos($this->val, '.');
605
+        if ($decPoint === false) {
606
+            return $this;
607
+        }
608
+        if ($this->val[0] == '-') {
609
+            return new Decimal(substr($this->val, 0, $decPoint));
610
+        }
611
+        elseif ($this->isInteger()) {
612
+            return new Decimal($this->val, 0);
613
+        }
614
+        else {
615
+            // non-negative number with non-zero fractional part
616
+            return new Decimal(bcadd(substr($this->val, 0, $decPoint), 1, 0));
617
+        }
618
+    }
619
+
620
+    /**
621
+     * @param string|int|float|Decimal|object $number
622
+     * @return Decimal the greater of this number and <tt>$number</tt>, preferably this number if numerically equal
623
+     */
624
+    public function max($number)
625
+    {
626
+        $arg = self::fromNumber($number);
627
+        if ($this->lessThan($arg)) {
628
+            return $arg;
629
+        }
630
+        else {
631
+            return $this;
632
+        }
633
+    }
634
+
635
+    /**
636
+     * @param string|int|float|Decimal|object $number
637
+     * @return Decimal the smaller of this number and <tt>$number</tt>, preferably this number if numerically equal
638
+     */
639
+    public function min($number)
640
+    {
641
+        $arg = self::fromNumber($number);
642
+        if ($this->greaterThan($arg)) {
643
+            return $arg;
644
+        }
645
+        else {
646
+            return $this;
647
+        }
648
+    }
649
+
650
+    /**
651
+     * @return Decimal a new decimal number, representing the square root of this number
652
+     * @throws UndefinedOperationException if this is a negative number
653
+     */
654
+    public function sqrt()
655
+    {
656
+        if ($this->isNaN()) {
657
+            return $this;
658
+        }
659
+        if ($this->isNegative()) {
660
+            throw new UndefinedOperationException('square root of negative number');
661
+        }
662
+        $scale = max($this->scale, self::MIN_SIG_DIGITS);
663
+        return new Decimal(bcsqrt($this->val, $scale + 1));
664
+    }
665
+
666
+
667
+    /**
668
+     * @return int|null the value of this number cast explicitly to <tt>int</tt>;
669
+     *                  for big numbers, this yields the maximal available integer value (i.e., <tt>PHP_INT_MAX</tt> or
670
+     *                    <tt>PHP_INT_MIN</tt>);
671 671
      *                  <tt>null</tt> is returned if this is the not-a-number
672
-	 */
673
-	public function toInt()
674
-	{
675
-		return ($this->val === null ? null : (int)$this->val);
676
-	}
677
-
678
-	/**
679
-	 * @return float the value of this number cast explicitly to <tt>float</tt>, or the float <tt>NAN</tt> value if this
680
-	 *                 is the not-a-number
681
-	 */
682
-	public function toFloat()
683
-	{
684
-		return ($this->val === null ? NAN : (float)$this->val);
685
-	}
686
-
687
-	/**
688
-	 * @return string the value of this number, or the special string <tt>'NaN'</tt> for the not-a-number value
689
-	 */
690
-	public function toString()
691
-	{
692
-		return ($this->val === null ? 'NaN' : $this->val);
693
-	}
694
-
695
-	/**
696
-	 * Converts the value to a {@link \GMP} object.
697
-	 *
698
-	 * Requires the `gmp` PHP extension.
699
-	 *
700
-	 * @return \GMP a {@link \GMP} object representing the same integer as this number
701
-	 * @throws UndefinedOperationException if this number is not an integer, i.e., has some non-zero fractional digits
702
-	 */
703
-	public function toGMP()
704
-	{
705
-		if (!$this->isInteger()) {
706
-			throw new UndefinedOperationException('toGMP() is only defined for integers');
707
-		}
708
-		$decPoint = strpos($this->val, '.');
709
-		$str = ($decPoint === false ? $this->val : substr($this->val, 0, $decPoint - 1));
710
-		return gmp_init($str);
711
-	}
712
-
713
-	public function __toString()
714
-	{
715
-		return $this->toString();
716
-	}
672
+     */
673
+    public function toInt()
674
+    {
675
+        return ($this->val === null ? null : (int)$this->val);
676
+    }
677
+
678
+    /**
679
+     * @return float the value of this number cast explicitly to <tt>float</tt>, or the float <tt>NAN</tt> value if this
680
+     *                 is the not-a-number
681
+     */
682
+    public function toFloat()
683
+    {
684
+        return ($this->val === null ? NAN : (float)$this->val);
685
+    }
686
+
687
+    /**
688
+     * @return string the value of this number, or the special string <tt>'NaN'</tt> for the not-a-number value
689
+     */
690
+    public function toString()
691
+    {
692
+        return ($this->val === null ? 'NaN' : $this->val);
693
+    }
694
+
695
+    /**
696
+     * Converts the value to a {@link \GMP} object.
697
+     *
698
+     * Requires the `gmp` PHP extension.
699
+     *
700
+     * @return \GMP a {@link \GMP} object representing the same integer as this number
701
+     * @throws UndefinedOperationException if this number is not an integer, i.e., has some non-zero fractional digits
702
+     */
703
+    public function toGMP()
704
+    {
705
+        if (!$this->isInteger()) {
706
+            throw new UndefinedOperationException('toGMP() is only defined for integers');
707
+        }
708
+        $decPoint = strpos($this->val, '.');
709
+        $str = ($decPoint === false ? $this->val : substr($this->val, 0, $decPoint - 1));
710
+        return gmp_init($str);
711
+    }
712
+
713
+    public function __toString()
714
+    {
715
+        return $this->toString();
716
+    }
717 717
 }
Please login to merge, or discard this patch.
src/Ivory/Value/Money.php 1 patch
Indentation   +59 added lines, -59 removed lines patch added patch discarded remove patch
@@ -14,25 +14,25 @@  discard block
 block discarded – undo
14 14
  */
15 15
 class Money
16 16
 {
17
-	private $amount;
18
-	private $unit;
17
+    private $amount;
18
+    private $unit;
19 19
 
20 20
 
21
-	/**
22
-	 * @param string $moneyStr the money quantity as a string;
23
-	 *                         the value must use <tt>$decimalSeparator</tt> as the decimal separator, but may use any
24
-	 *                           thousands separators;
25
-	 *                         the unit may occur either before or after the actual value, or not at all;
26
-	 *                         whitespace is ignored altogether except for the unit
27
-	 * @param string $decimalSeparator
28
-	 * @return Money the representation of the given quantity
29
-	 * @throws \InvalidArgumentException if the string does not conform to the recognized syntax (which is iff it does
30
-	 *                                     not contain any decimal digit)
31
-	 */
32
-	public static function fromString($moneyStr, $decimalSeparator)
33
-	{
34
-		$ds = preg_quote($decimalSeparator, '~');
35
-		$re = '~^ \s*
21
+    /**
22
+     * @param string $moneyStr the money quantity as a string;
23
+     *                         the value must use <tt>$decimalSeparator</tt> as the decimal separator, but may use any
24
+     *                           thousands separators;
25
+     *                         the unit may occur either before or after the actual value, or not at all;
26
+     *                         whitespace is ignored altogether except for the unit
27
+     * @param string $decimalSeparator
28
+     * @return Money the representation of the given quantity
29
+     * @throws \InvalidArgumentException if the string does not conform to the recognized syntax (which is iff it does
30
+     *                                     not contain any decimal digit)
31
+     */
32
+    public static function fromString($moneyStr, $decimalSeparator)
33
+    {
34
+        $ds = preg_quote($decimalSeparator, '~');
35
+        $re = '~^ \s*
36 36
                 (\D+?)?                             # optional unit before the amount
37 37
                 \s*
38 38
                 (\d(?:[^' . $ds . '\d]*\d+)*)       # the integer part of the amount
@@ -42,56 +42,56 @@  discard block
 block discarded – undo
42 42
                 \s*
43 43
                 $~x';
44 44
 
45
-		if (!preg_match($re, $moneyStr, $m)) {
46
-			throw new \InvalidArgumentException('$moneyStr');
47
-		}
45
+        if (!preg_match($re, $moneyStr, $m)) {
46
+            throw new \InvalidArgumentException('$moneyStr');
47
+        }
48 48
 
49
-		$amount = preg_replace('~\D+~', '', $m[2]);
50
-		if (isset($m[3]) && $m[3] !== '') {
51
-			$amount .= '.' . preg_replace('~\D+~', '', $m[3]);
52
-		}
49
+        $amount = preg_replace('~\D+~', '', $m[2]);
50
+        if (isset($m[3]) && $m[3] !== '') {
51
+            $amount .= '.' . preg_replace('~\D+~', '', $m[3]);
52
+        }
53 53
 
54
-		$decimal = Decimal::fromNumber($amount);
55
-		$unit = ($m[1] !== '' ? $m[1] : (isset($m[4]) && $m[4] !== '' ? $m[4] : null));
54
+        $decimal = Decimal::fromNumber($amount);
55
+        $unit = ($m[1] !== '' ? $m[1] : (isset($m[4]) && $m[4] !== '' ? $m[4] : null));
56 56
 
57
-		return new Money($decimal, $unit);
58
-	}
57
+        return new Money($decimal, $unit);
58
+    }
59 59
 
60
-	public static function fromNumber($amount, $unit = null)
61
-	{
62
-		$decimal = Decimal::fromNumber($amount);
63
-		if ($decimal->isNaN()) {
64
-			throw new \InvalidArgumentException('$amount cannot be NaN');
65
-		}
60
+    public static function fromNumber($amount, $unit = null)
61
+    {
62
+        $decimal = Decimal::fromNumber($amount);
63
+        if ($decimal->isNaN()) {
64
+            throw new \InvalidArgumentException('$amount cannot be NaN');
65
+        }
66 66
 
67
-		return new Money($decimal, $unit);
68
-	}
67
+        return new Money($decimal, $unit);
68
+    }
69 69
 
70 70
 
71
-	private function __construct($amount, $unit)
72
-	{
73
-		$this->amount = $amount;
74
-		$this->unit = $unit;
75
-	}
71
+    private function __construct($amount, $unit)
72
+    {
73
+        $this->amount = $amount;
74
+        $this->unit = $unit;
75
+    }
76 76
 
77
-	/**
78
-	 * @return Decimal
79
-	 */
80
-	public function getAmount()
81
-	{
82
-		return $this->amount;
83
-	}
77
+    /**
78
+     * @return Decimal
79
+     */
80
+    public function getAmount()
81
+    {
82
+        return $this->amount;
83
+    }
84 84
 
85
-	/**
86
-	 * @return string|null the money unit (e.g., <tt>$</tt>), or <tt>null</tt> if no unit is specified for this amount
87
-	 */
88
-	public function getUnit()
89
-	{
90
-		return $this->unit;
91
-	}
85
+    /**
86
+     * @return string|null the money unit (e.g., <tt>$</tt>), or <tt>null</tt> if no unit is specified for this amount
87
+     */
88
+    public function getUnit()
89
+    {
90
+        return $this->unit;
91
+    }
92 92
 
93
-	public function __toString()
94
-	{
95
-		return "{$this->amount} " . ($this->unit === null ? '(no unit)' : $this->unit);
96
-	}
93
+    public function __toString()
94
+    {
95
+        return "{$this->amount} " . ($this->unit === null ? '(no unit)' : $this->unit);
96
+    }
97 97
 }
Please login to merge, or discard this patch.