FloatOutputTrait::validateDoubleRange()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 23
ccs 15
cts 15
cp 1
rs 9.2222
cc 6
nc 8
nop 3
crap 6
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 3
            );
89
        }
90
    }
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 6
            );
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 3
            );
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 3
            );
125
        }
126
    }
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
     * @note The parameters are passed via reference from the main logical method for performance reasons.
136
     */
137 3
    protected function improvePoorFloatOutput(&$from, &$to, &$precision)
138
    {
139 3
        $epsilon = $this->calculateEpsilon($precision);
140
141 3
        $toIsTheMaximum = abs($this->getMaxNumber() - $to) < $epsilon;
142 3
        $fromIsTheZero = abs(0.0 - $from) < $epsilon;
143
144 3
        $fromIsTheMinimumPlusOne = abs(($this->getMinNumber() + 1.0) - $from) < $epsilon;
145 3
        $toIsTheZero = abs(0.0 - $to) < $epsilon;
146
147
        // Improves the overall calculation quality for range calls
148 3
        if ($toIsTheMaximum && $fromIsTheZero) {
149 3
            $from = 0.01;
150 3
        } elseif ($toIsTheZero && $fromIsTheMinimumPlusOne) {
151 3
            $to = 0.01;
152
        }
153
    }
154
155
    /**
156
     * Generate a probability format float number between 0.0 and 1.0.
157
     *
158
     * Note: Passing `null` will use the global system precision value.
159
     *
160
     * @param null|int $precision Rounding precision (default => 10).
161
     *
162
     * @return float Randomly generated probability value.
163
     * @throws \Exception Validation errors.
164
     */
165 3
    public function getProbability($precision = 10)
166
    {
167 3
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
168
169 3
        $this->validatePositiveInteger($precision, true);
170
171 3
        $number = $this->getInt(0, $this->getMaxNumber());
172
173 3
        $isNotRangeBoarders = ($number !== 0 && $number !== $this->getMaxNumber());
174
175 3
        $number = ($number === 0) ? 0.00 : ($number === $this->getMaxNumber() ? 100.00 : (float)$number);
176
177 3
        $number = $isNotRangeBoarders ? round($number / (float)$this->getMaxNumber(), $precision) : $number;
178
179 3
        return $number;
180
    }
181
182
    /**
183
     * Generate a random float number in a certain range.
184
     *
185
     * Note: Passing `null` will use the default parameter value or for precision the global system value.
186
     *
187
     * @param null|float|int $from The lowest value to be returned (default => 0.0).
188
     * @param null|float|int $to The highest value to be returned (default => (float)$this->getMaxNumber()).
189
     * @param null|int $precision Rounding precision (default => 8).
190
     *
191
     * @return float Randomly generated float value.
192
     * @throws \Exception Validation errors.
193
     */
194 18
    public function getFloat($from = 0.0, $to = null, $precision = 8)
195
    {
196 18
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
197
198 18
        $this->validatePositiveInteger($precision, true);
199
200 18
        $this->validateNumericOrDefault($from, $to);
201
202 15
        $from = ($from === null) ? 0.0 : (float)$from;
203 15
        $to = ($to === null) ? (float)$this->getMaxNumber() : (float)$to;
204
205 15
        $this->validateDoubleRange($from, $to, $precision);
206
207 3
        $this->improvePoorFloatOutput($from, $to, $precision);
208
209
        // Minimum precision for probability fetching
210 3
        $scope = ($precision > 14) ? $precision : 14;
211
212 3
        return round($from + $this->getProbability($scope) * abs($to - $from), $precision);
213
    }
214
215
    /**
216
     * Generate a percentage format float number between 0.0 and 100.0.
217
     *
218
     * Note: Passing `null` will use the global system precision value.
219
     *
220
     * @param null|int $precision Rounding precision (default => 2).
221
     * @param bool|int $lowerTheScope Flag for using a smaller calculation range.
222
     *
223
     * @return float Randomly generated percentage value.
224
     * @throws \Exception Validation errors.
225
     */
226 3
    public function getPercent($precision = 2, $lowerTheScope = false)
227
    {
228 3
        $precision = ($precision === null) ? self::$systemPrecision : $precision;
229
230 3
        $this->validatePositiveInteger($precision, true);
231
232 3
        if ($lowerTheScope) {
233 3
            $number = $this->calculateLowQualityPercent(9999); // 0-9999
234
        } else {
235
            // Minimum precision for probability fetching
236 3
            $scope = ($precision > 14) ? $precision : 14;
237
238 3
            $number = $this->getProbability($scope) * 100.00;
239
        }
240
241 3
        return round($number, $precision);
242
    }
243
}
244