pycustomrand 0.0.2
Библиотека для генерации псевдослучайных чисел на основе времени
Requires Python: >=3.8

<div align="center">

  # PyCustomRand
  
  ![Python](https://custom-icon-badges.demolab.com/badge/Python-3.8+-blue?logo=pythonn)
  [![Last Commit](https://img.shields.io/github/last-commit/n1xsi/pycustomrand.svg)](https://github.com/n1xsi/pycustomrand/commits/main)
  [![Run Tests](https://github.com/n1xsi/PyCustomRand/actions/workflows/python-app.yml/badge.svg)](https://github.com/n1xsi/PyCustomRand/actions/workflows/python-app.yml)

</div>

🌐 **Languages:**
*🇷🇺 Русский* | <a href="README.en.md">🇬🇧 English</a>

<br>

**PyCustomRand** — это Python-библиотека для генерации **псевдослучайных чисел**, основанная на алгоритме, использующем системное время с наносекундной точностью.

Данный авторский проект создан для изучения алгоритмов и альтернативных подходов к генерации случайных чисел и их округлении.
Меня не устраивало, что модуль `random` в Python генерирует недостаточно случайные числа ( *особенно при нескольких последовательных генерациях* ), а встроенный в Python модуль `round` довольно "грубо" округляет числа ( *`round(1.5)` = 2 и в то же время `round(2.5)` = 2* ), поэтому я решил написать *свою* библиотеку для генерации псевдослучайных чисел — более простую и с наибольшей энтропией.

> [!WARNING]
> PyCustomRand *пока* **не является** криптографически стойкой библиотекой и **не предназначена** для использования в системах безопасности!
> Используйте [secrets](https://docs.python.org/3/library/secrets.html#module-secrets) модуль.

## 📌 Зачем нужна эта библиотека, если есть уже встроенные в Python `random` и `round`?
PyCustomRand писался как альтернатива этим двум встроенным модулям. Вот главные особенности и возможности библиотеки:

* Данная библиотека **проще** написана — весь код закомментирован, лёгок и понятен. В основе реализации генератора псевдослучайных чисел лежит *несложный* алгоритм, использующий системное время, поэтому библиотеку легко кастомизировать, расширять или дополнять под себя.
* В большинстве своём PyCustomRand выдаёт более *"энтропичный"* результат при нескольких генерациях подряд по сравнению со встроенным `random`-модулем.
  * <details>
      <summary>📊 Сравнение с диаграммами</summary>
      
      PyCustomRand демонстрирует **на 173%** более высокую кучность распределения по сравнению со стандартной библиотекой `random` в тестах на **1.000.000** итераций.

      <img src="https://i.imgur.com/qxYdxmD.png">
      
      Данные для диаграммы брались из тестирования, которое проводилось с помощью встроенного модуля диагностики (`check_distribution`). По результатам PyCustomRand показал максимальное отклонение **в 2.7 раза** меньше, чем стандартный `random` (0.0267% против 0.0730%).

      <table align="center" style="border-collapse: collapse;">
        <!-- Строка с заголовками -->
        <tr>
          <th align="center" style="font-family: sans-serif">
            PyCustomRand
          </th>
          <th align="center" style="font-family: sans-serif">
            Python Built-in
          </th>
        </tr>
        <!-- Строка с картинками -->
        <tr>
          <td align="center" width="50%">
              <img src="https://i.imgur.com/kH26JEO.jpeg" width="100%" alt="PyCustomRand">
          </td>
          <td align="center" width="50%">
              <img src="https://i.imgur.com/LdFoREN.jpeg" width="100%" alt="Python Built-in">
          </td>
        </tr>
      </table>
    </details>
* Библиотека включает в себя все самые главные функции из [оригинальной](https://docs.python.org/3/library/random.html) Python-библиотеки, но только местами улучшенные и упрощённые (например: `random_integer` может выдавать сгенерированный результат, делимый на заданное число из диапазона; результат функции `random_float` можно округлять и так далее)
  * <details>
      <summary>📑 Список функций</summary>
    
      *   Генерация целых чисел (`gen_random_number`, `randrange`, `random_integer`).
      *   Генерация вещественных чисел (`random`, `random_float`).
      *   Поддержка статистических распределений: Нормальное (Гаусс), Треугольное, Экспоненциальное, Биномиальное.
      *   Инструменты для последовательностей: выбор случайного элемента (`choice`), выборка с весами (`choices`), перемешивание (`shuffle`) и выборка уникальных элементов (`sample`).
      *   Инициализация ("посев") последовательности генератора псевдослучайных чисел (`set_seed`, `_get_next_seed_state`).
    </details>
* Также библиотека имеет дополнительные утилиты, полезные для разработки веба/игр: генерация UUID v4, случайных HEX-цветов (например, `#ff05a1`), случайных байт, случайных булевых значений (`True`/`False`) с настраиваемым шансом.
* Имеется собственный модуль округления — `true_round`. Функция округляет числа привычным математическим способом (0.5 всегда вверх по модулю), а также исправляет погрешности плавающей точки (например, "проблему 2.675").
* Код покрыт unit-тестами (чему соответствует badge в начале README), также имеется встроенный модуль диагностики (`check_distribution`), который позволяет в любой момент проверить равномерность распределения генератора.

Да, у проекта всё же имеются некоторые минусы:
* Рандом, основанный на времени, требует небольшого ожидания (`time.sleep()` на 0.1 микросекунду), соответственно, при гигантских итерациях PyCustomRand будет немного уступать встроенному модулю `random` во времени.
* Некоторые функции библиотеки PyCustomRand (функции выборки, распределений) настолько просто написаны, что могут быть недостаточно оптимизированными для гигантских выборок (в добавок учитывается небольшое накапливающееся ожидание из-за проблемы выше).

## 📦 Установка
Установка библиотеки происходит через стандартный менеджер пакетов для Python:
```bash
pip install pycustomrand
```

<details>
  <summary>➕ Альтернативные способы установки</summary>
  Если у вас возникли проблемы с пакетным менеджером Python или прочие ошибки, то PyCustomRand можно установить другими способами:
  
  <br>

  * Скачивание пакета на официальной странице PyPI:
  https://pypi.org/project/pycustomrand/#files

  *  Скачивание пакета **напрямую** в разделе *Releases*:
  https://github.com/n1xsi/PyCustomRand/releases

  *  Клонирование репозитория:
  ```bash
  git clone https://github.com/n1xsi/PyCustomRand.git
  ```
</details>


## 📚 Документация
Ниже представлено описание всех доступных функций библиотеки с примерами. Для удобства они могут импороваться напрямую из пакета (`from pycustomrand import FUNC_NAME`).

### 1. Управление состоянием (Seeding)
Инициализация генератора позволяет получать воспроизводимые последовательности (например, для тестов или сохранения генерации мира).

* `set_seed(seed=None)`
Устанавливает начальное состояние генератора.
  * `seed`: Любой объект, который преобразуется в строку — число/строка/список и др. Если `None` — "посев" сбрасывается, и используется системное время (случайная последовательность).

* `_get_next_seed_state(current_seed)`
Внутренняя функция для обновления состояния сида (LCG алгоритм).

<details>
    <summary>🧩 Примеры 1</summary>
  
```python
from pycustomrand import set_seed, random

print(random())  # Случайное число, например, 0.3730190220377659

set_seed("test")  # Установление сида для тестов
print(random())   # Первый результат функции после установления сида "test" ВСЕГДА будет 0.1647608190844912
print(random())   # Второй результат функции тоже ВСЕГДА будет 0.943716375817365 (применилась функция _get_next_seed_state(current_seed="test"))

set_seed()       # Обнуление сида
print(random())  # Случайное число, например, 0.2458062256227575
```

</details>

### 2. Целые числа
* `gen_random_number(length=1)`
Генерирует число заданной длины `length`, склеивая случайные цифры.

* `randrange(start, stop=None, step=1)`
Полный аналог стандартного `range` — возвращает элемент из `range(start, stop, step)`. Верхняя граница **не включается**. Можно указывать один агрумент.

* `random_integer(start, end, step=1)` (алиас: `randint`)
Возвращает случайное целое число `N`, такое что `start <= N <= end` (**обе границы включены**).
  * `step`: Шаг генерации. Например, `step=2` вернет только чётные (или нечётные) числа. Необязательный аргумент.

<details>
    <summary>🧩 Примеры 2</summary>
  
```python
from pycustomrand import gen_random_number, randrange, random_integer

print(gen_random_number(5))  # Случайное число длиной 5, например: 92103

print(randrange(10))        # Случайное число из диапазона [0, 10)
print(randrange(1, 10))     # Случайное число из диапазона [1, 10)
print(randrange(0, 10, 2))  # Случайное чётное число из диапазона [0, 10)

print(random_integer(1, 10))     # Случайное целое число из диапазона [1, 10]
print(random_integer(0, 10, 2))  # Случайное чётное целое число из диапазона [0, 10]

# randint - алиас для random_integer, работает при "import pycustomrand", "from pycustomrand import *" или "from pycustomrand import randint"
# randint(1, 10) равноценно random_integer(1, 10)
```

</details>

### 3. Вещественные числа
* `random()`
Возвращает случайное число `float` в диапазоне `[0.0, 1.0)`.

* `random_float(start, end, digits=None)`
Возвращает число `float` в диапазоне `[start, end)` или `[start, end]` в зависимости от округления (при наличии `digits`).
  * `start`, `end`: Начало и конец диапазона, могут быть как целыми числами, так и числами с плавающей точкой.
  * `digits`: Если число указано, результат будет округлён до этого количества знаков с помощью `true_round`.

<details>
    <summary>🧩 Примеры 3</summary>
  
```python
from pycustomrand import random, random_float

print(random())  # Случайное число в диапазоне [0, 1), например 0.2260351121787103

print(random_float(0, 10))            # Случайное число с плавающей точкой в диапазоне [0, 10), например 4.014874483651235
print(random_float(0, 10, digits=3))  # То же самое, только с округлением до 3-ёх знаков, например 6.722
```

</details>

### 4. Последовательности
* `choice(array)`
Возвращает один случайный элемент из последовательности.

* `choices(array, k, weights=None)`
Возвращает список из `k` случайных элементов с возможностью повторений.
  * `weights`: Список весов (вероятностей) для каждого элемента (соответствует по индексу). Если `None` — все элементы считаются равными по весу.

* `shuffle(array)`
Перемешивает изменяемую последовательность (список) на месте (in-place).

* `sample(array, k, counts=None)`
Возвращает список из `k` **уникальных** случайных элементов.
  * `counts`: Список с количеством повторений для каждого элемента массива (соответствует по индексу).

<details>
    <summary>🧩 Примеры 4</summary>
  
```python
from pycustomrand import choice, choices, shuffle, sample

array = ["apple", "banana", "cherry", "orange"]

print(choice(array))  # Случайный элемент из массива, например "cherry"

print(choices(array, k=2))  # Список из k случайных элементов из массива, например ['apple', 'cherry']
print(choices(array, k=2, weights=[1, 2, 3, 4]))  # Список из k случайных элементов из массива с весами, например ['orange', 'banana']
# "orange" (с весом 4) будет выпдать в четыре раза чаще, чем "apple" (с весом 1)

shuffle(array)  # Перемешивает массив на месте
print(array)    # Например ['cherry', 'banana', 'apple', 'orange']

print(sample(array, k=2))  # Список из k уникальных случайных элементов из массива, например ['cherry', 'apple']
print(sample(array, k=2, counts=[1, 2, 3, 4]))  # Список из k уникальных случайных элементов из массива с весами, например ['banana', 'orange']
# "orange" (с количеством в массиве 4) будет выпдать в четыре раза больше, чем "apple" (количество в массиве 1)
```

</details>

### 5. Вероятностные распределения
Эти функции используются для моделирования реальных процессов (физика, экономика, игры).

* `triangular(low=0.0, high=1.0, mode=None)` — Треугольное распределение.
Генерирует число в диапазоне `[low, high]`, но чаще всего выпадает число `mode` (вершина треугольника).
Используется в простом моделировании и играх. *Пример*: Урон мечом от 5 до 15, но чаще всего бьёт на 10 — `triangular(5, 15, 10)`.

* `gauss(mu=0.0, sigma=1.0)` — Нормальное распределение (Колокол Гаусса).
Большинство значений группируется вокруг среднего (`mu`), а `sigma` (стандартное отклонение) показывает разброс.
Используется для моделирования природных явлений. *Примеры*: Рост людей, IQ, ошибки измерений, разброс пуль при стрельбе.
  * `mu`: Среднее значение (центр).
  * `sigma`: Стандартное отклонение (разброс).

* `expovariate(lambd=1.0)` — Экспоненциальное распределение.
Описывает время между событиями в процессе Пуассона. Часто выпадают маленькие значения, редко — большие (длинный хвост).
Используется в таймерах. *Примеры*: Время между приходом автобусов. В игре спавн монстров — обычно они лезут часто, но иногда случаются долгие затишья.
  * `lambd`: Интенсивность событий (должна быть > 0).

* `binomialvariate(n=1, p=0.5)` — Биномиальное распределение (алиас: `binomial`).
Количество успехов в серии из `n` независимых испытаний с вероятностью успеха `p`.
Используется в математике и моделировании финансов. *Примеры*: Сколько раз сейчас выпадет орёл, если подбросить монету 10 раз — `binomialvariate(n=10, p=0.5)`.

<details>
    <summary>🧩 Примеры 5</summary>
  
```python
from pycustomrand import triangular, gauss, expovariate, binomialvariate

print(triangular(low=0.0, high=1.0, mode=0.5))  # Случайное число с плавающей точкой из треугольного распределения (диапазон [0.0, 1.0])
# Чаще всего выпадает значение ±0.5, например: 0.6508330793068139

print(gauss(mu=0.0, sigma=1.0))  # Случайное число с плавающей точкой из нормального "колокольного" распределения (общий диапазон 0.0 ± ~3.0)
# Чаще всего выпадает значение 0.0 ± 1.0, например: 1.0665809173007805 или -1.650128903962443

print(expovariate(lambd=1.0))  # Случайное число с плавающей точкой из экспоненциального распределения (общий диапазон 0.0 ± ~4.0)
# Чаще всего выпадает наименьшее значение от 0.0 до 1.0, например: 0.405175514533143

print(binomialvariate(n=10, p=0.5))  # Случайное число из биномиального распределения (диапазон [0, 10]), например: 5
# Чем меньше вероятность p - тем чаще будет выпадать число из первой половины диапазона [0, 10], больше - большее из второй половины

# binomial - алиас для binomialvariate, работает при "import pycustomrand", "from pycustomrand import *" или "from pycustomrand import binomial"
# binomial(n=10, p=0.5)) равноценно binomialvariate(n=10, p=0.5)
```

</details>

### 6. Утилиты (Extras)
* `random_bytes(count)`: Генерация случайных байт в количестве `count`.

* `random_uuid4()`: Генерация уникального идентификатора (UUID v4). 

* `random_color_hex()`: Случайный цвет в формате HEX.

* `random_bool(true_chance=0.5)`: Возвращает True или False c вероятностью `true_chance`.

<details>
    <summary>🧩 Примеры 6</summary>
  
```python
from pycustomrand import random_bytes, random_uuid4, random_color_hex, random_bool

print(random_bytes(10))    # Случайные 10 байтов, например: b'\x8b\x91\x19\xe42\xcd\x80\x82b\xba'

print(random_uuid4())      # Случайный UUID v4, например: '0e1d2eeb-1627-4cfa-8130-afe74e1c5ce9'

print(random_color_hex())  # Cлучайный цвет в формате hex, например: '#0ca3e7'

print(random_bool())       # Случайное значение True/False, например: True
```

</details>


### 🎯 О модуле `true_round`
Модуль решает проблему "банковского округления" в Python 3, где `round(x.5)` округляется до ближайшего чётного числа.
`true_round` использует **честное математическое округление** (отсюда и название "true" — истинное/честное).

Для удобства модуль может быть импортирован напрямую из пакета (`from pycustomrand import true_round`).

| Число | Стандартный `round()` | PyCustomRand `true_round()` | Примечание |
| :--- | :---: | :---: | :--- |
| **2.5** | 2 | **3** | Математическое округление (0.5 → вверх) |
| **3.5** | 4 | **4** | Совпадает |
| **2.675** (до 2-ух знаков) | 2.67 | **2.68** | Исправление погрешности Floating Point Error |
| **1.005** (до 2-ух знаков) | 1.0  | **1.01** | Корректная обработка граничных значений |
| **0.00049** (до 3-ёх знаков) | 0.0 | **0.001** | Точность при работе с малыми числами |


## 🧪 Тестирование и Диагностика
PyCustomRand предоставляет внутренние инструменты для проверки работоспособности библиотеки и качества энтропии генератора случайных чисел.

### 1. Запуск всех Unit-тестов
Чтобы убедиться, что все функции работают корректно и библиотека установлена правильно, запустите встроенные тесты, находясь в корне репозитория:

```bash
python -m unittest discover tests
```

### 1.1. Запуск Unit-тестов конкретного модуля
Отдельный запуск проверки модуля генератора случайных чисел:

```bash
python -m unittest tests/test_random.py
```

Отдельный запуск проверки модуля округления чисел:

```bash
python -m unittest tests/test_round.py
```

### 1.2. Запуск конкретного теста в конкретном модуле
Если вы модифицируете библиотеку, и у вас постоянно не проходит проверку какой-то один конкретный тест, то вы можете не тратить время на запуск всех тестов — вам достаточно запустить один конкретный тест конкретного модуля, например:

```python
from tests.test_round import TestTrueRound
import unittest

# Запуск теста "округления отрицательных чисел" для функции true_round
unittest.main(defaultTest='TestTrueRound.test_negative_numbers')
```

### 2. Проверка распределения (Диагностика)
В библиотеку встроен декоратор `@check_distribution` (находится в `pycustomrand.diagnostics`). Он позволяет визуально оценить равномерность генерации чисел. Пример использования декоратора:

```python
from pycustomrand.diagnostics import check_distribution
import pycustomrand as pcr

# Декоратор запустит функцию 100.000 раз и разобьёт результаты на 20 корзин
@check_distribution(count=100_000, buckets=20)
def check_float_random():
    return pcr.random()

if __name__ == "__main__":
    check_float_random()
```

Результат в консоли показывает:
* На какой сейчас итерации находится диагностика (каждые 5 секунд);
* Полное время выполнения генерации;
* Таблицу с количеством попаданий в каждую "корзину" (диапазон);
* Процентное отклонение от идеального распределения;  
* Вердикт: "Отличное равномерное распределение" или предупреждение о перекосах.

<details>
  <summary>📊 Пример результата диагностики</summary>
  <div align="center">
    <img src="https://i.imgur.com/WUzDRfY.png" width="40%">
  </div>
</details>


## 📂 Структура репозитория
Проект имеет следующую файловую структуру:

```
PyCustomRand/                # Корень репозитория
├── .github/
│   └── workflows/
│       └── python-app.yml   # Конфигурация GH Actions: автоматический запуск тестов при каждом Push/PR
│
├── pycustomrand/            # Исходный код пакета
│   ├── __init__.py            # Точка входа: инициализация пакета и алиасы функций
│   ├── custom_round.py        # Реализация алгоритма математического округления (true_round)
│   ├── diagnostics.py         # Декоратор для проверки равномерности распределения чисел
│   └── random_generator.py    # Ядро библиотеки: класс PseudoRandom и вся логика генерации
│
├── tests/                   # Набор Unit-тестов
│   ├── __init__.py            # Пустой файл (чтобы тесты видели друг друга)
│   ├── test_random.py         # Тесты для генератора, seed, диапазонов и утилит
│   └── test_round.py          # Тесты для проверки корректности округления
│
├── .gitignore               # Правила исключения файлов из Git (venv, кэш, логи)
├── LICENSE                  # Текст лицензии MIT
├── pyproject.toml           # Конфигурационный файл сборки пакета для PyPI
├── README.en.md             # Документация проекта (на Английском)
└── README.md                # Документация проекта (на Русском)
```

## 🤝 Вклад в проект (Контрибуция)
Приветствуется любая помощь в развитии библиотеки. Если у вас есть идеи по улучшению алгоритмов, оптимизации кода или вы нашли ошибку — смело создавайте Issue/Pull Request.

**Как внести свой вклад:**
1. Сделайте **Fork** репозитория;
2. Создайте новую ветку для вашей фичи (`git checkout -b feature/SomeFeature`);
3. Внесите изменения;
4. **Запустите тесты**, чтобы убедиться, что ваши изменения ничего не сломали:
   ```bash
   python -m unittest discover tests
   ```
5. "Запушьте" изменения (`git push origin feature/SomeFeature`);
6. Откройте **Pull Request**.

## 📄 Лицензия
Этот проект является программным обеспечением с открытым исходным кодом и распространяется под лицензией **MIT License**.

    Copyright © 2025 Ivan (n1xsi)

    Данная лицензия разрешает любому лицу получать копию данного программного обеспечения и сопутствующей документации бесплатно,
    использовать его без ограничений, включая право копировать, модифицировать, публиковать и распространять копии ПО.

Полный текст лицензии доступен в файле [LICENSE](LICENSE).