Passed
Branch merging-leagues-tournaments (a75688)
by Benedikt
07:29
created

Random::extractEntropy()   C

Complexity

Conditions 12
Paths 5

Size

Total Lines 29
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
c 0
b 0
f 0
dl 0
loc 29
rs 6.9666
cc 12
nc 5
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
4
5
namespace Tfboe\FmLib\Helpers;
6
7
8
use Tfboe\FmLib\Exceptions\Internal;
9
10
/**
11
 * Class Random
12
 * @package Tfboe\FmLib\Helpers
13
 */
14
class Random
15
{
16
//<editor-fold desc="Fields">
17
  private const ENTROPY = [
18
    1 => 2,
19
    2 => 4,
20
    3 => 8
21
  ];
22
23
  /**
24
   * @var string
25
   */
26
  private $hexString;
27
28
  /**
29
   * @var int
30
   */
31
  private $remainingBitsFirstChar;
32
//</editor-fold desc="Fields">
33
34
//<editor-fold desc="Constructor">
35
  /**
36
   * Random constructor.
37
   * @param string $hexString
38
   */
39
  public function __construct(string $hexString)
40
  {
41
    $this->hexString = $hexString;
42
    $this->remainingBitsFirstChar = 4;
43
  }
44
//</editor-fold desc="Constructor">
45
46
//<editor-fold desc="Public Methods">
47
  /**
48
   * @param int $max
49
   * @param int $min
50
   * @return int
51
   */
52
  public function extractEntropy(int $max, int $min = 0): int
53
  {
54
    Internal::assert($max <= PHP_INT_MAX && $min >= PHP_INT_MIN && $min <= $max);
55
    $maxInt = (PHP_INT_MAX >> 1);
56
    if (($max > $maxInt && ($min <= 0 || $max - $min > $maxInt)) ||
57
      ($min < -$maxInt && ($max >= 0 || $max - $min > $maxInt)) || ($max - $min) > $maxInt) {
58
      //calculate floor(($max - $min) / 16) considering overflows
59
      $max16 = $max % 16;
60
      $min16 = $min % 16;
61
      $max1 = (($max - $max16) >> 4) - (($min - $min16) >> 4);
62
      $rest = $max16 - $min16;
63
      if ($rest >= 16) {
64
        $max1++;
65
        $rest -= 16;
66
      }
67
68
      $z1 = $this->extractEntropy($max1);
69
      $max2 = 15;
70
      if ($z1 == $max1) {
71
        $max2 = $rest;
72
      }
73
      $z2 = $this->extractEntropy($max2);
74
      return (($min + 8 * $z1) + 8 * $z1) + $z2;
75
    }
76
    $range = $max - $min;
77
    Internal::assert($range <= PHP_INT_MAX);
78
    $bits = static::countBits($range);
79
80
    return $min + ($this->extractEntropyByBits($bits) % ($range + 1));
81
  }
82
83
  /**
84
   * @param int $bits
85
   * @return int
86
   */
87
  public function extractEntropyByBits(int $bits): int
88
  {
89
    Internal::assert($bits >= 0);
90
    if ($bits === 0 || empty($this->hexString)) {
91
      return 0; //no entropy
92
    }
93
    $value = 0;
94
    if ($bits >= $this->remainingBitsFirstChar) {
95
      //extract full positions
96
      $fullExtractLength = 1 + (($bits - $this->remainingBitsFirstChar) >> 2);
97
      $value = hexdec(substr($this->hexString, 0, $fullExtractLength));
98
      $bits -= $this->remainingBitsFirstChar + ($fullExtractLength - 1) * 4;
99
      $this->remainingBitsFirstChar = 4;
100
      $this->hexString = substr($this->hexString, $fullExtractLength);
101
      if (empty($this->hexString)) {
102
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
103
      }
104
    }
105
    if ($bits > 0) {
106
      $digit = hexdec(substr($this->hexString, 0, 1));
107
      $r = self::ENTROPY[$bits];
108
      $value *= $r;
109
      $this->remainingBitsFirstChar -= $bits;
110
      $value += $digit >> $this->remainingBitsFirstChar;
111
      $this->hexString = dechex($digit & (self::ENTROPY[$this->remainingBitsFirstChar] - 1))
112
        . substr($this->hexString, 1);
113
    }
114
    return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value could return the type double which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
115
  }
116
117
  /**
118
   * @param $str
119
   * @return Random
120
   */
121
  public static function stringToRandom($str): Random
122
  {
123
    return new Random(hash("sha256", $str));
124
  }
125
//</editor-fold desc="Public Methods">
126
127
//<editor-fold desc="Private Methods">
128
  /**
129
   * @param $n
130
   * @return int
131
   */
132
  private static function countBits(int $n): int
133
  {
134
    $count = 0;
135
    while ($n > 0) {
136
      $count++;
137
      $n >>= 1;
138
    }
139
    return $count;
140
  }
141
//</editor-fold desc="Private Methods">
142
}