Passed
Branch master (fadfee)
by Tony Karavasilev (Тони
36:03 queued 16:00
created

FloatOutputTrait::getFloat()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
ccs 0
cts 0
cp 0
rs 9.6111
c 0
b 0
f 0
cc 5
nc 16
nop 3
crap 30
1
<?php
2
3
/**
4
 * Trait implementation of float format generation for generator services.
5
 */
6
7
namespace CryptoManana\Core\Traits\Randomness;
8
9
use \CryptoManana\Core\Traits\Randomness\RandomnessTrait as RandomnessSpecification;
10
11
/**
12
 * Trait FloatOutputTrait - Reusable implementation of `FloatOutputInterface`.
13
 *
14
 * @see \CryptoManana\Core\Interfaces\Randomness\FloatOutputInterface The abstract specification.
15
 *
16
 * @package CryptoManana\Core\Traits\Randomness
17
 *
18
 * @mixin RandomnessSpecification
19
 */
20
trait FloatOutputTrait
21
{
22
    /**
23
     * Forcing the implementation of the software abstract randomness.
24
     *
25
     * {@internal Forcing the implementation of `AbstractRandomness`. }}
26
     */
27 6
    use RandomnessSpecification;
28
29 6
    /**
30
     * Internal method for calculating the machine epsilon value based on the used precision.
31
     *
32 6
     * Note: Passing `null` will use the global system precision value.
33
     *
34 6
     * @param null|int $precision The wanted precision for the machine epsilon.
35 6
     *
36
     * @return float The machine epsilon used for floating number comparison operations.
37
     */
38 6
    protected function calculateEpsilon($precision = null)
39
    {
40
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
41
42
        // Calculate epsilon based on precision digits
43
        $epsilon = 0.1;
44
45
        for ($i = 1; $i < $precision; $i++) {
46
            $epsilon *= 0.1;
47
        }
48
49
        return $epsilon;
50 15
    }
51
52 15
    /**
53
     * Generate a low quality percentage format float number between 0.0 and 100.0.
54 15
     *
55 6
     * @param int $max The upper scope number used for internal generation.
56 6
     *
57
     * @return float Randomly generated low quality percentage value.
58
     */
59
    protected function calculateLowQualityPercent($max)
60 9
    {
61 3
        $number = $this->getInt(0, $max);
62 3
63
        $isNotRangeBoarders = ($number !== 0 && $number !== $max);
64
65
        $number = ($number === 0) ? 0.00 : ($number === $max) ? 100.00 : $number;
66 6
67
        $number = $isNotRangeBoarders ? ($number / $max) * 100.00 : $number;
68 6
69
        return $number;
70 6
    }
71 3
72 3
    /**
73
     * Internal method for double range supported types validation.
74
     *
75 3
     * @param int|float|null $from The minimum number in the wanted range.
76
     * @param int|float|null $to The maximum number in the wanted range.
77
     *
78
     * @throws \Exception Validation errors.
79
     */
80
    protected function validateNumericOrDefault($from, $to)
81
    {
82
        $fromInvalidType = !in_array(gettype($from), ['integer', 'double', 'NULL']);
83
        $toInvalidType = !in_array(gettype($to), ['integer', 'double', 'NULL']);
84
85
        if ($fromInvalidType || $toInvalidType) {
86
            throw new \DomainException(
87 3
                "The provided values are of invalid type."
88
            );
89 3
        }
90
    }
91 3
92
    /**
93 3
     * Internal method for double range validation.
94
     *
95 3
     * @param int|float $from The minimum number in the wanted range.
96
     * @param int|float $to The maximum number in the wanted range.
97 3
     * @param null|int $precision The used precision for comparison.
98
     *
99 3
     * @throws \Exception Validation errors.
100
     */
101 3
    protected function validateDoubleRange($from, $to, $precision = 14)
102
    {
103
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
104
105
        if ($from < (float)$this->getMinNumber() || $to > (float)$this->getMaxNumber()) {
106
            throw new \DomainException(
107
                "The provided values are out of the supported range."
108
            );
109
        }
110
111
        if ($from > $to) {
112
            throw new \LogicException(
113
                "The chosen generation maximum is less or equal the provided minimum value."
114
            );
115
        }
116 18
117
        $epsilon = $this->calculateEpsilon($precision);
118 18
119
        $difference = abs($from - $to);
120 18
121
        if ($difference < $epsilon) {
122 18
            throw new \LogicException(
123 18
                "The chosen generation maximum is less or equal the provided minimum value."
124
            );
125 18
        }
126 3
    }
127 3
128
    /**
129
     * Improve the quality of the algorithm used for floating numbers generation.
130
     *
131 15
     * @param float $from The lowest value to be returned.
132 15
     * @param float $to The highest value to be returned.
133
     * @param int $precision Rounding precision for epsilon calculation.
134 15
     */
135
    protected function improvePoorFloatOutput(&$from, &$to, &$precision)
136 3
    {
137
        $epsilon = $this->calculateEpsilon($precision);
138 3
139 3
        $toIsTheMaximum = abs($this->getMaxNumber() - $to) < $epsilon;
140
        $fromIsTheZero = abs(0.0 - $from) < $epsilon;
141 3
142 3
        $fromIsTheMinimumPlusOne = abs(($this->getMinNumber() + 1.0) - $from) < $epsilon;
143
        $toIsTheZero = abs(0.0 - $to) < $epsilon;
144
145 3
        // Improves the overall calculation quality for range calls
146 3
        if ($toIsTheMaximum && $fromIsTheZero) {
147 3
            $from = 0.01;
148 3
        } elseif ($toIsTheZero && $fromIsTheMinimumPlusOne) {
149
            $to = 0.01;
150
        }
151
    }
152 3
153
    /**
154 3
     * Generate a probability format float number between 0.0 and 1.0.
155
     *
156
     * Note: Passing `null` will use the global system precision value.
157
     *
158
     * @param null|int $precision Rounding precision (default => 10).
159
     *
160
     * @return float Randomly generated probability value.
161
     * @throws \Exception Validation errors.
162
     */
163
    public function getProbability($precision = 10)
164
    {
165
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
166
167
        $this->validatePositiveInteger($precision, true);
168 3
169
        $number = $this->getInt(0, $this->getMaxNumber());
170 3
171
        $isNotRangeBoarders = ($number !== 0 && $number !== $this->getMaxNumber());
172 3
173
        $number = ($number === 0) ? 0.00 : ($number === $this->getMaxNumber()) ? 100.00 : (float)$number;
174 3
175 3
        $number = $isNotRangeBoarders ? round($number / (float)$this->getMaxNumber(), $precision) : $number;
176
177 3
        return $number;
178
    }
179 3
180
    /**
181 3
     * Generate a random float number in a certain range.
182
     *
183 3
     * Note: Passing `null` will use the default parameter value or for precision the global system value.
184
     *
185
     * @param null|float|int $from The lowest value to be returned (default => 0.0).
186 3
     * @param null|float|int $to The highest value to be returned (default => (float)$this->getMaxNumber()).
187
     * @param null|int $precision Rounding precision (default => 8).
188 3
     *
189
     * @return float Randomly generated float value.
190
     * @throws \Exception Validation errors.
191 3
     */
192
    public function getFloat($from = 0.0, $to = null, $precision = 8)
193
    {
194
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
195
196
        $this->validatePositiveInteger($precision, true);
197
198
        $this->validateNumericOrDefault($from, $to);
199
200
        $from = ($from === null) ? 0.0 : (float)$from;
201
        $to = ($to === null) ? (float)$this->getMaxNumber() : (float)$to;
202
203
        $this->validateDoubleRange($from, $to, $precision);
204
205
        $this->improvePoorFloatOutput($from, $to, $precision);
206
207
        // Minimum precision for probability fetching
208
        $scope = ($precision > 14) ? $precision : 14;
209
210
        return round($from + $this->getProbability($scope) * abs($to - $from), $precision);
211
    }
212
213
    /**
214
     * Generate a percentage format float number between 0.0 and 100.0.
215
     *
216
     * Note: Passing `null` will use the global system precision value.
217
     *
218
     * @param null|int $precision Rounding precision (default => 2).
219
     * @param bool|int $lowerTheScope Flag for using a smaller calculation range.
220
     *
221
     * @return float Randomly generated percentage value.
222
     * @throws \Exception Validation errors.
223
     */
224
    public function getPercent($precision = 2, $lowerTheScope = false)
225
    {
226
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
227
228
        $this->validatePositiveInteger($precision, true);
229
230
        if ($lowerTheScope) {
231
            $number = $this->calculateLowQualityPercent(9999); // 0-9999
232
        } else {
233
            // Minimum precision for probability fetching
234
            $scope = ($precision > 14) ? $precision : 14;
235
236
            $number = $this->getProbability($scope) * 100.00;
237
        }
238
239
        return round($number, $precision);
240
    }
241
}
242