Passed
Push — master ( 742b46...ae2e05 )
by Tony Karavasilev (Тони
20:10
created

FloatOutputTrait::getFloat()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 5

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 19
ccs 3
cts 3
cp 1
rs 9.6111
c 0
b 0
f 0
cc 5
nc 16
nop 3
crap 5
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
    use RandomnessSpecification;
28
29
    /**
30
     * Internal method for calculating the machine epsilon value based on the used precision.
31
     *
32
     * Note: Passing `null` will use the global system precision value.
33
     *
34
     * @param null|int $precision The wanted precision for the machine epsilon.
35
     *
36
     * @return float The machine epsilon used for floating number comparison operations.
37
     */
38 6
    protected function calculateEpsilon($precision = null)
39
    {
40 6
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
41
42
        // Calculate epsilon based on precision digits
43 6
        $epsilon = 0.1;
44
45 6
        for ($i = 1; $i < $precision; $i++) {
46 6
            $epsilon *= 0.1;
47
        }
48
49 6
        return $epsilon;
50
    }
51
52
    /**
53
     * Generate a low quality percentage format float number between 0.0 and 100.0.
54
     *
55
     * @param int $max The upper scope number used for internal generation.
56
     *
57
     * @return float Randomly generated low quality percentage value.
58
     */
59 3
    protected function calculateLowQualityPercent($max)
60
    {
61 3
        $number = $this->getInt(0, $max);
62
63 3
        $isNotRangeBoarders = ($number !== 0 && $number !== $max);
64
65 3
        $number = ($number === 0) ? 0.00 : ($number === $max) ? 100.00 : $number;
66
67 3
        $number = $isNotRangeBoarders ? ($number / $max) * 100.00 : $number;
68
69 3
        return $number;
70
    }
71
72
    /**
73
     * Internal method for double range supported types validation.
74
     *
75
     * @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 18
    protected function validateNumericOrDefault($from, $to)
81
    {
82 18
        $fromInvalidType = !in_array(gettype($from), ['integer', 'double', 'NULL']);
83 18
        $toInvalidType = !in_array(gettype($to), ['integer', 'double', 'NULL']);
84
85 18
        if ($fromInvalidType || $toInvalidType) {
86 3
            throw new \DomainException(
87 3
                "The provided values are of invalid type."
88
            );
89
        }
90 15
    }
91
92
    /**
93
     * Internal method for double range validation.
94
     *
95
     * @param int|float $from The minimum number in the wanted range.
96
     * @param int|float $to The maximum number in the wanted range.
97
     * @param null|int $precision The used precision for comparison.
98
     *
99
     * @throws \Exception Validation errors.
100
     */
101 15
    protected function validateDoubleRange($from, $to, $precision = 14)
102
    {
103 15
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
104
105 15
        if ($from < (float)$this->getMinNumber() || $to > (float)$this->getMaxNumber()) {
106 6
            throw new \DomainException(
107 6
                "The provided values are out of the supported range."
108
            );
109
        }
110
111 9
        if ($from > $to) {
112 3
            throw new \LogicException(
113 3
                "The chosen generation maximum is less or equal the provided minimum value."
114
            );
115
        }
116
117 6
        $epsilon = $this->calculateEpsilon($precision);
118
119 6
        $difference = abs($from - $to);
120
121 6
        if ($difference < $epsilon) {
122 3
            throw new \LogicException(
123 3
                "The chosen generation maximum is less or equal the provided minimum value."
124
            );
125
        }
126 3
    }
127
128
    /**
129
     * Improve the quality of the algorithm used for floating numbers generation.
130
     *
131
     * @param float $from The lowest value to be returned.
132
     * @param float $to The highest value to be returned.
133
     * @param int $precision Rounding precision for epsilon calculation.
134
     */
135
    protected function improvePoorFloatOutput(&$from, &$to, &$precision)
136
    {
137
        $epsilon = $this->calculateEpsilon($precision);
138 3
139
        $toIsTheMaximum = abs($this->getMaxNumber() - $to) < $epsilon;
140 3
        $fromIsTheZero = abs(0.0 - $from) < $epsilon;
141
142 3
        $fromIsTheMinimumPlusOne = abs(($this->getMinNumber() + 1.0) - $from) < $epsilon;
143
        $toIsTheZero = abs(0.0 - $to) < $epsilon;
144 3
145
        // Improves the overall calculation quality for range calls
146 3
        if ($toIsTheMaximum && $fromIsTheZero) {
147
            $from = 0.01;
148 3
        } elseif ($toIsTheZero && $fromIsTheMinimumPlusOne) {
149
            $to = 0.01;
150 3
        }
151
    }
152 3
153
    /**
154
     * 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 18
        $this->validatePositiveInteger($precision, true);
168
169 18
        $number = $this->getInt(0, $this->getMaxNumber());
170
171 18
        $isNotRangeBoarders = ($number !== 0 && $number !== $this->getMaxNumber());
172
173 18
        $number = ($number === 0) ? 0.00 : ($number === $this->getMaxNumber()) ? 100.00 : (float)$number;
174
175 15
        $number = $isNotRangeBoarders ? round($number / (float)$this->getMaxNumber(), $precision) : $number;
176 15
177
        return $number;
178 15
    }
179
180 3
    /**
181
     * Generate a random float number in a certain range.
182 3
     *
183 3
     * Note: Passing `null` will use the default parameter value or for precision the global system value.
184
     *
185 3
     * @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
     *
189 3
     * @return float Randomly generated float value.
190 3
     * @throws \Exception Validation errors.
191 3
     */
192 3
    public function getFloat($from = 0.0, $to = null, $precision = 8)
193
    {
194
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
195
196 3
        $this->validatePositiveInteger($precision, true);
197
198 3
        $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 3
213
    /**
214 3
     * Generate a percentage format float number between 0.0 and 100.0.
215
     *
216 3
     * Note: Passing `null` will use the global system precision value.
217
     *
218 3
     * @param null|int $precision Rounding precision (default => 2).
219 3
     * @param bool|int $lowerTheScope Flag for using a smaller calculation range.
220
     *
221
     * @return float Randomly generated percentage value.
222 3
     * @throws \Exception Validation errors.
223
     */
224 3
    public function getPercent($precision = 2, $lowerTheScope = false)
225
    {
226
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
227 3
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