Passed
Push — master ( 855cb2...742b46 )
by Tony Karavasilev (Тони
01:26
created

FloatOutputTrait::getFloat()   B

Complexity

Conditions 9
Paths 48

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9

Importance

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