23
23
*/
24
24
class PercentToLocalizedStringTransformer implements DataTransformerInterface
25
25
{
26
+ /**
27
+ * Rounds a number towards positive infinity.
28
+ *
29
+ * Rounds 1.4 to 2 and -1.4 to -1.
30
+ */
31
+ const ROUND_CEILING = \NumberFormatter::ROUND_CEILING ;
32
+
33
+ /**
34
+ * Rounds a number towards negative infinity.
35
+ *
36
+ * Rounds 1.4 to 1 and -1.4 to -2.
37
+ */
38
+ const ROUND_FLOOR = \NumberFormatter::ROUND_FLOOR ;
39
+
40
+ /**
41
+ * Rounds a number away from zero.
42
+ *
43
+ * Rounds 1.4 to 2 and -1.4 to -2.
44
+ */
45
+ const ROUND_UP = \NumberFormatter::ROUND_UP ;
46
+
47
+ /**
48
+ * Rounds a number towards zero.
49
+ *
50
+ * Rounds 1.4 to 1 and -1.4 to -1.
51
+ */
52
+ const ROUND_DOWN = \NumberFormatter::ROUND_DOWN ;
53
+
54
+ /**
55
+ * Rounds to the nearest number and halves to the next even number.
56
+ *
57
+ * Rounds 2.5, 1.6 and 1.5 to 2 and 1.4 to 1.
58
+ */
59
+ const ROUND_HALF_EVEN = \NumberFormatter::ROUND_HALFEVEN ;
60
+
61
+ /**
62
+ * Rounds to the nearest number and halves away from zero.
63
+ *
64
+ * Rounds 2.5 to 3, 1.6 and 1.5 to 2 and 1.4 to 1.
65
+ */
66
+ const ROUND_HALF_UP = \NumberFormatter::ROUND_HALFUP ;
67
+
68
+ /**
69
+ * Rounds to the nearest number and halves towards zero.
70
+ *
71
+ * Rounds 2.5 and 1.6 to 2, 1.5 and 1.4 to 1.
72
+ */
73
+ const ROUND_HALF_DOWN = \NumberFormatter::ROUND_HALFDOWN ;
74
+
26
75
const FRACTIONAL = 'fractional ' ;
27
76
const INTEGER = 'integer ' ;
28
77
@@ -31,6 +80,8 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
31
80
self ::INTEGER ,
32
81
];
33
82
83
+ protected $ roundingMode ;
84
+
34
85
private $ type ;
35
86
private $ scale ;
36
87
@@ -42,7 +93,7 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
42
93
*
43
94
* @throws UnexpectedTypeException if the given value of type is unknown
44
95
*/
45
- public function __construct (int $ scale = null , string $ type = null )
96
+ public function __construct (int $ scale = null , string $ type = null , ? int $ roundingMode = self :: ROUND_HALF_UP )
46
97
{
47
98
if (null === $ scale ) {
48
99
$ scale = 0 ;
@@ -52,12 +103,17 @@ public function __construct(int $scale = null, string $type = null)
52
103
$ type = self ::FRACTIONAL ;
53
104
}
54
105
106
+ if (null === $ roundingMode ) {
107
+ $ roundingMode = self ::ROUND_HALF_UP ;
108
+ }
109
+
55
110
if (!\in_array ($ type , self ::$ types , true )) {
56
111
throw new UnexpectedTypeException ($ type , implode ('", " ' , self ::$ types ));
57
112
}
58
113
59
114
$ this ->type = $ type ;
60
115
$ this ->scale = $ scale ;
116
+ $ this ->roundingMode = $ roundingMode ;
61
117
}
62
118
63
119
/**
@@ -166,7 +222,7 @@ public function reverseTransform($value)
166
222
}
167
223
}
168
224
169
- return $ result ;
225
+ return $ this -> round ( $ result) ;
170
226
}
171
227
172
228
/**
@@ -179,7 +235,58 @@ protected function getNumberFormatter()
179
235
$ formatter = new \NumberFormatter (\Locale::getDefault (), \NumberFormatter::DECIMAL );
180
236
181
237
$ formatter ->setAttribute (\NumberFormatter::FRACTION_DIGITS , $ this ->scale );
238
+ $ formatter ->setAttribute (\NumberFormatter::ROUNDING_MODE , $ this ->roundingMode );
182
239
183
240
return $ formatter ;
184
241
}
242
+
243
+ /**
244
+ * Rounds a number according to the configured scale and rounding mode.
245
+ *
246
+ * @param int|float $number A number
247
+ *
248
+ * @return int|float The rounded number
249
+ */
250
+ private function round ($ number )
251
+ {
252
+ if (null !== $ this ->scale && null !== $ this ->roundingMode ) {
253
+ // shift number to maintain the correct scale during rounding
254
+ $ roundingCoef = pow (10 , $ this ->scale );
255
+
256
+ if (self ::FRACTIONAL == $ this ->type ) {
257
+ $ roundingCoef *= 100 ;
258
+ }
259
+
260
+ // string representation to avoid rounding errors, similar to bcmul()
261
+ $ number = (string ) ($ number * $ roundingCoef );
262
+
263
+ switch ($ this ->roundingMode ) {
264
+ case self ::ROUND_CEILING :
265
+ $ number = ceil ($ number );
266
+ break ;
267
+ case self ::ROUND_FLOOR :
268
+ $ number = floor ($ number );
269
+ break ;
270
+ case self ::ROUND_UP :
271
+ $ number = $ number > 0 ? ceil ($ number ) : floor ($ number );
272
+ break ;
273
+ case self ::ROUND_DOWN :
274
+ $ number = $ number > 0 ? floor ($ number ) : ceil ($ number );
275
+ break ;
276
+ case self ::ROUND_HALF_EVEN :
277
+ $ number = round ($ number , 0 , PHP_ROUND_HALF_EVEN );
278
+ break ;
279
+ case self ::ROUND_HALF_UP :
280
+ $ number = round ($ number , 0 , PHP_ROUND_HALF_UP );
281
+ break ;
282
+ case self ::ROUND_HALF_DOWN :
283
+ $ number = round ($ number , 0 , PHP_ROUND_HALF_DOWN );
284
+ break ;
285
+ }
286
+
287
+ $ number /= $ roundingCoef ;
288
+ }
289
+
290
+ return $ number ;
291
+ }
185
292
}
0 commit comments