PluginProbe
Packeta / 2.1
Packeta v2.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Core / Rounder.php

Rounder.php in Packeta 2.1, at src/Packetery/Core/Rounder.php

176 lines 4.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Rounder
4 *
5 * @package Packetery
6 */
7
8 declare( strict_types=1 );
9
10 namespace Packetery\Core;
11
12 use InvalidArgumentException;
13
14 /**
15 * Class Rounder
16 *
17 * Class for rounding numbers e.g. prices
18 *
19 * @package Packetery
20 */
21 class Rounder {
22
23 public const ROUND_UP = 1;
24 public const ROUND_DOWN = -1;
25 public const DONT_ROUND = 0;
26 public const ROUNDING_TYPES = [ self::ROUND_UP, self::ROUND_DOWN, self::DONT_ROUND ];
27
28 /**
29 * Returns rounded amount.
30 *
31 * @param float $amount Amount.
32 * @param int $roundingType Type of rounding.
33 * @param int $precision Precision (number of digits after decimal point).
34 *
35 * @return float
36 * @throws InvalidArgumentException InvalidArgumentException.
37 */
38 public static function round( float $amount, int $roundingType, int $precision ): float {
39 if ( ( $precision < 0 ) || ! in_array( $roundingType, self::ROUNDING_TYPES, true ) ) {
40 throw new InvalidArgumentException(
41 sprintf(
42 'Precision should be non-negative number, roundingType should be one of %s.',
43 implode( ', ', self::ROUNDING_TYPES )
44 )
45 );
46 }
47
48 $amount *= 10 ** $precision;
49 $amount = self::roundFloat( $amount, $roundingType );
50 $amount /= ( 10 ** $precision );
51
52 return $amount;
53 }
54
55 /**
56 * Rounds amount up according to precision.
57 *
58 * @param float $amount Amount to round.
59 * @param int $precision Precision (number of digits after decimal point).
60 *
61 * @return float
62 */
63 public static function roundUp( float $amount, int $precision = 0 ): float {
64 return self::round( $amount, self::ROUND_UP, $precision );
65 }
66
67 /**
68 * Rounds amount up according to precision.
69 *
70 * @param float $amount Amount to round.
71 * @param int $precision Precision (number of digits after decimal point).
72 *
73 * @return float
74 */
75 public static function roundDown( float $amount, int $precision = 0 ): float {
76 return self::round( $amount, self::ROUND_DOWN, $precision );
77 }
78
79 /**
80 * Returns rounded amount to multiple of $number.
81 *
82 * @param float $amount Amount.
83 * @param int $roundingType Type of rounding.
84 * @param int $divisor Round to multiple of this number.
85 *
86 * @return float
87 * @throws InvalidArgumentException InvalidArgumentException.
88 */
89 public static function roundToMultipleOfNumber( float $amount, int $roundingType, int $divisor ): float {
90 if ( ( $divisor < 1 ) || ! in_array( $roundingType, self::ROUNDING_TYPES, true ) ) {
91 throw new InvalidArgumentException(
92 sprintf(
93 'Divisor should be positive number, roundingType should be one of %s.',
94 implode( ', ', self::ROUNDING_TYPES )
95 )
96 );
97 }
98
99 $amount /= $divisor;
100 $amount = self::roundFloat( $amount, $roundingType );
101
102 return $divisor * $amount;
103 }
104
105 /**
106 * Returns rounded amount according to currency and currency constraints of Packeta API
107 *
108 * @param float $amount Amount.
109 * @param string $currencyCode Currency code.
110 * @param int $roundingType Type of rounding.
111 *
112 * @return float
113 * @throws InvalidArgumentException InvalidArgumentException.
114 */
115 public static function roundByCurrency( float $amount, string $currencyCode, int $roundingType ): float {
116 if ( ! in_array( $roundingType, self::ROUNDING_TYPES, true ) ) {
117 throw new InvalidArgumentException(
118 sprintf(
119 'RoundingType should should be one of %s.',
120 implode( ', ', self::ROUNDING_TYPES )
121 )
122 );
123 }
124
125 if ( $currencyCode === 'CZK' ) {
126 return self::round( $amount, $roundingType, 0 );
127 }
128
129 if ( $currencyCode === 'HUF' ) {
130 return self::roundToMultipleOfNumber( $amount, $roundingType, 5 );
131 }
132
133 return self::round( $amount, $roundingType, 2 );
134 }
135
136 /**
137 * Returns rounded amount.
138 *
139 * @param float $amount Amount to round.
140 * @param int $roundingType Type of rounding.
141 *
142 * @return float
143 */
144 private static function roundFloat( float $amount, int $roundingType ): float {
145 if ( $roundingType === self::ROUND_DOWN ) {
146 return floor( self::sanitizeFloat( $amount ) );
147 }
148 if ( $roundingType === self::ROUND_UP ) {
149 return ceil( self::sanitizeFloat( $amount ) );
150 }
151
152 return $amount;
153 }
154
155 /**
156 * Solves float precision problems, for example that (1.15 * 100) !== (float) 115.
157 * Works with locales not using decimal dot.
158 *
159 * @param float $number Number to sanitize.
160 *
161 * @return float
162 */
163 private static function sanitizeFloat( float $number ): float {
164 $oldLocale = setlocale( LC_NUMERIC, '0' );
165 if ( $oldLocale !== false ) {
166 setlocale( LC_NUMERIC, 'C' );
167 }
168 $newNumber = (float) ( (string) $number );
169 if ( $oldLocale !== false ) {
170 setlocale( LC_NUMERIC, $oldLocale );
171 }
172
173 return $newNumber;
174 }
175 }
176