Repository: andreypomortsev/yndx-python-handbook

Issue:
Feature: Add 5 solutions for 5.1 A-E
1. Выложить решения для задач A,B,C,D,E параграфа 5.1
2. Покрыть тестами 100% решений. 
3. Обновить README.md.

Discussion:

Solution patch:
diff --git a/README.md b/README.md
index d1228b83..48defa4d 100644
--- a/README.md
+++ b/README.md
@@ -207,7 +207,7 @@ def main():
 
 <summary><h3>2.1 Ввод и вывод данных. Операции с числами, строками. Форматирование</h3></summary>
 
-- [2.1 Ввод и вывод данных. Операции с числами, строками. Форматирование](https://education.yandex.ru/handbook/python/article/vvod-i-vyvod-dannykh-operatsii-s-chislami-strokami-formatirovaniye)
+- [Теория Ввод и вывод данных. Операции с числами, строками. Форматирование](https://education.yandex.ru/handbook/python/article/vvod-i-vyvod-dannykh-operatsii-s-chislami-strokami-formatirovaniye)
   
 ### [Тестовые данные для задач](./tests/data/test_data_21.py)
   
@@ -239,7 +239,7 @@ def main():
 <details>
 <summary><h3>2.2 Условный оператор</h3></summary>
 
-- [2.2 Условный оператор](https://education.yandex.ru/handbook/python/article/uslovnyy-operator)
+- [Теория Условный оператор](https://education.yandex.ru/handbook/python/article/uslovnyy-operator)
 
 ### [Тестовые данные для задач](./tests/data/test_data_22.py)
 
@@ -271,7 +271,7 @@ def main():
 <details>
 <summary><h3>2.3 Циклы</h3></summary>
 
-- [2.3 Циклы](https://education.yandex.ru/handbook/python/article/cikly)
+- [Теория Циклы](https://education.yandex.ru/handbook/python/article/cikly)
 
 ### [Тестовые данные для задач](./tests/data/test_data_23.py)
 
@@ -303,7 +303,7 @@ def main():
 <details>
 <summary><h3>2.4 Вложенные циклы</h3></summary>
 
-- [2.4 Вложенные циклы](https://education.yandex.ru/handbook/python/article/vlozhennye-cikly)
+- [Теория Вложенные циклы](https://education.yandex.ru/handbook/python/article/vlozhennye-cikly)
 
 ### [Тестовые данные для задач](./tests/data/test_data_24.py)
 
@@ -335,7 +335,7 @@ def main():
 <details>
 <summary><h3>3.1 Строки, кортежи, списки</h3></summary>
 
-- [3.1 Строки, кортежи, списки](https://education.yandex.ru/handbook/python/article/stroki-kortezhi-spiski)
+- [Теория Строки, кортежи, списки](https://education.yandex.ru/handbook/python/article/stroki-kortezhi-spiski)
 
 ### [Тестовые данные для задач](./tests/data/test_data_31.py)
 
@@ -367,7 +367,7 @@ def main():
 <details>
 <summary><h3>3.2 Множества, словари</h3></summary>
 
-- [3.2 Множества, словари](https://education.yandex.ru/handbook/python/article/mnozhestva-slovari)
+- [Теория Множества, словари](https://education.yandex.ru/handbook/python/article/mnozhestva-slovari)
 
 ### [Тестовые данные для задач](./tests/data/test_data_32.py)
 
@@ -399,7 +399,7 @@ def main():
 <details>
 <summary><h3>3.3 Списочные выражения. Модель памяти для типов языка Python</h3></summary>
 
-- [3.3 Списочные выражения. Модель памяти для типов языка Python](https://education.yandex.ru/handbook/python/article/spisochnye-vyrazheniya-model-pamyati-dlya-tipov-yazyka-python)
+- [Теория Списочные выражения. Модель памяти для типов языка Python](https://education.yandex.ru/handbook/python/article/spisochnye-vyrazheniya-model-pamyati-dlya-tipov-yazyka-python)
 
 ### [Тестовые данные для задач](./tests/data/test_data_33.py)
 
@@ -421,7 +421,7 @@ def main():
 <details>
 <summary><h3>3.4 Встроенные возможности по работе с коллекциями</h3></summary>
 
-- [3.4 Встроенные возможности по работе с коллекциями](https://education.yandex.ru/handbook/python/article/vstroennye-vozmozhnosti-po-rabote-s-kollekciyami)
+- [Теория Встроенные возможности по работе с коллекциями](https://education.yandex.ru/handbook/python/article/vstroennye-vozmozhnosti-po-rabote-s-kollekciyami)
 
 ### [Тестовые данные для задач](./tests/data/test_data_34.py)
 
@@ -453,7 +453,7 @@ def main():
 <details>
 <summary><h3>3.5 Потоковый ввод/вывод. Работа с текстовыми файлами. JSON</h3></summary>
 
-- [3.5 Потоковый ввод/вывод. Работа с текстовыми файлами. JSON](https://education.yandex.ru/handbook/python/article/potokovyj-vvodvyvod-rabota-s-tekstovymi-fajlami-json)
+- [Теория Потоковый ввод/вывод. Работа с текстовыми файлами. JSON](https://education.yandex.ru/handbook/python/article/potokovyj-vvodvyvod-rabota-s-tekstovymi-fajlami-json)
 
 ### [Тестовые данные для задач](./tests/data/test_data_35.py)
 
@@ -485,7 +485,7 @@ def main():
 <details>
 <summary><h3>4.1 Функции. Области видимости. Передача параметров в функции</h3></summary>
 
-- [4.1 Функции. Области видимости. Передача параметров в функции](https://education.yandex.ru/handbook/python/article/funkcii-oblasti-vidimosti-peredacha-parametrov-v-funkcii)
+- [Теория Функции. Области видимости. Передача параметров в функции](https://education.yandex.ru/handbook/python/article/funkcii-oblasti-vidimosti-peredacha-parametrov-v-funkcii)
 
 ### [Тестовые данные для задач](./tests/data/test_data_41.py)
 
@@ -507,7 +507,7 @@ def main():
 <details>
 <summary><h3>4.2 Позиционные и именованные аргументы. Функции высших порядков. Лямбда-функции</h3></summary>
 
-- [4.2 Позиционные и именованные аргументы. Функции высших порядков. Лямбда-функции](https://education.yandex.ru/handbook/python/article/pozicionnye-i-imenovannye-argumenty-funkcii-vysshih-poryadkov-lyambda-funkcii)
+- [Теория Позиционные и именованные аргументы. Функции высших порядков. Лямбда-функции](https://education.yandex.ru/handbook/python/article/pozicionnye-i-imenovannye-argumenty-funkcii-vysshih-poryadkov-lyambda-funkcii)
 
 ### [Тестовые данные для задач](./tests/data/test_data_42.py)
 
@@ -529,7 +529,7 @@ def main():
 <details>
 <summary><h3>4.3. Рекурсия. Декораторы. Генераторы</h3></summary>
 
-- [4.3. Рекурсия. Декораторы. Генераторы](https://education.yandex.ru/handbook/python/article/rekursiya-dekoratory-generatory)
+- [Теория Рекурсия. Декораторы. Генераторы](https://education.yandex.ru/handbook/python/article/rekursiya-dekoratory-generatory)
 
 ### [Тестовые данные для задач](./tests/data/test_data_43.py)
 
@@ -548,6 +548,28 @@ def main():
 
 </details>
 
+<details>
+<summary><h3>5.1 Объектная модель Python. Классы, поля и методы</h3></summary>
+
+- [Теория Объектная модель Python. Классы, поля и методы](https://education.yandex.ru/handbook/python/article/obuektnaya-model-python-klassy-polya-i-metody)
+
+### [Тестовые данные для задач](./tests/data/test_data_51.py)
+
+| Решение              | Тесты                |
+|----------------------|----------------------|
+| А. [Классная точка](./solutions/5.1/51_a.py) | [✅](./tests/5.1/test_51_a.py) |
+| B. [Классная точка 2.0](./solutions/5.1/51_b.py) | [✅](./tests/5.1/test_51_b.py) |
+| C. [Не нажимай красную кнопку!](./solutions/5.1/51_c.py) | [✅](./tests/5.1/test_51_c.py) |
+| D. [Работа не волк](./solutions/5.1/51_d.py) | [✅](./tests/5.1/test_51_d.py) |
+| E. [Классный прямоугольник](./solutions/5.1/51_e.py) | [✅](./tests/5.1/test_51_e.py) |
+| F. [Классный прямоугольник 2.0](./solutions/5.1/51_f.py) | [❌](./tests/5.1/test_51_f.py) |
+| G. [Классный прямоугольник 3.0](./solutions/5.1/51_g.py) | [❌](./tests/5.1/test_51_g.py) |
+| H. [Шашки](./solutions/5.1/51_h.py) | [❌](./tests/5.1/test_51_h.py) |
+| I. [Очередь](./solutions/5.1/51_i.py) | [❌](./tests/5.1/test_51_i.py) |
+| J. [Стэк](./solutions/5.1/51_j.py) | [❌](./tests/5.1/test_51_j.py) |
+
+</details>
+
 ## Создание Issue
 
 Если вы обнаружили ошибку, очепятку или баг в проекте, пожалуйста, создайте новый issue, следуя этому шаблону:
diff --git a/solutions/5.1/51_a.py b/solutions/5.1/51_a.py
new file mode 100644
index 00000000..45c5e61c
--- /dev/null
+++ b/solutions/5.1/51_a.py
@@ -0,0 +1,4 @@
+class Point:
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
diff --git a/solutions/5.1/51_b.py b/solutions/5.1/51_b.py
new file mode 100644
index 00000000..17ed6fd3
--- /dev/null
+++ b/solutions/5.1/51_b.py
@@ -0,0 +1,12 @@
+class Point:
+    def __init__(self, x: float | int, y: float | int):
+        self.x = x
+        self.y = y
+
+    def move(self, dx: float | int, dy: float | int) -> None:
+        self.x += dx
+        self.y += dy
+
+    def length(self, other: "Point") -> float | int:
+        distance_squared = (other.x - self.x) ** 2 + (other.y - self.y) ** 2
+        return round(distance_squared**0.5, 2)
diff --git a/solutions/5.1/51_c.py b/solutions/5.1/51_c.py
new file mode 100644
index 00000000..440ae564
--- /dev/null
+++ b/solutions/5.1/51_c.py
@@ -0,0 +1,10 @@
+class RedButton:
+    def __init__(self):
+        self.counter = 0
+
+    def click(self) -> None:
+        print("Тревога!")
+        self.counter += 1
+
+    def count(self) -> int:
+        return self.counter
diff --git a/solutions/5.1/51_d.py b/solutions/5.1/51_d.py
new file mode 100644
index 00000000..914c4c6d
--- /dev/null
+++ b/solutions/5.1/51_d.py
@@ -0,0 +1,27 @@
+class Programmer:
+    def __init__(self, name: str, position: str):
+        self.name = name
+        self.position = position
+        self.position_salary = {"Junior": 10, "Middle": 15, "Senior": 20}
+        self.bucks_per_hour = self.position_salary[self.position]
+        self.hours = 0
+        self.salary = 0
+
+    def work(self, hours: int) -> None:
+        self.hours += hours
+        self.salary += self.bucks_per_hour * hours
+
+    def rise(self) -> None:
+        match self.bucks_per_hour:
+            case 10:
+                self.position = "Middle"
+                self.bucks_per_hour = self.position_salary[self.position]
+            case 15:
+                self.position = "Senior"
+                self.bucks_per_hour = self.position_salary[self.position]
+            case _:
+                self.bucks_per_hour += 1
+
+    def info(self) -> str:
+        info = f"{self.name} {self.hours}ч. {self.salary}тгр."
+        return info
diff --git a/solutions/5.1/51_e.py b/solutions/5.1/51_e.py
new file mode 100644
index 00000000..cee62206
--- /dev/null
+++ b/solutions/5.1/51_e.py
@@ -0,0 +1,18 @@
+class Rectangle:
+    def __init__(
+        self,
+        point_one: tuple[float | int, float | int],
+        point_two: tuple[float | int, float | int],
+    ):
+        self.x_one, self.y_one = point_one
+        self.x_two, self.y_two = point_two
+        self.height = abs(self.y_one - self.y_two)
+        self.width = abs(self.x_one - self.x_two)
+
+    def perimeter(self) -> float | int:
+        calculated_perimeter = (self.height + self.width) * 2
+        return round(calculated_perimeter, 2)
+
+    def area(self) -> float | int:
+        calculated_area = self.height * self.width
+        return round(calculated_area, 2)


Test patch:
diff --git a/tests/5.1/test_51_a.py b/tests/5.1/test_51_a.py
new file mode 100644
index 00000000..133e2ba1
--- /dev/null
+++ b/tests/5.1/test_51_a.py
@@ -0,0 +1,27 @@
+from types import ModuleType
+from typing import Callable
+
+import pytest
+
+from tests import utils
+from tests.data.test_data_51 import point_init_test_data
+
+
+@pytest.mark.parametrize(
+    "x, y, _",
+    point_init_test_data,
+    ids=[i[-1] for i in point_init_test_data],
+)
+def test_point_class(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    x: int | float,
+    y: int | float,
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+    point = solution_module.Point(x, y)
+
+    assert point.x == x
+    assert point.y == y
diff --git a/tests/5.1/test_51_b.py b/tests/5.1/test_51_b.py
new file mode 100644
index 00000000..c0ba2657
--- /dev/null
+++ b/tests/5.1/test_51_b.py
@@ -0,0 +1,82 @@
+from types import ModuleType
+from typing import Callable, Tuple
+
+import pytest
+
+from tests import utils
+from tests.data.test_data_51 import (point_init_test_data, point_length,
+                                     point_move_test_data)
+
+
+@pytest.mark.parametrize(
+    "x, y, _",
+    point_init_test_data,
+    ids=[i[-1] for i in point_init_test_data],
+)
+def test_point_class_init(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    x: int | float,
+    y: int | float,
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+    point = solution_module.Point(x, y)
+
+    assert (point.x, point.y) == (x, y)
+
+
+@pytest.mark.parametrize(
+    "x_y, x_y_move, _",
+    point_move_test_data,
+    ids=[i[-1] for i in point_move_test_data],
+)
+def test_point_move(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    x_y: Tuple[int | float],
+    x_y_move: Tuple[int | float],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    x, y = x_y
+    point = solution_module.Point(x, y)
+
+    assert (point.x, point.y) == (x, y)
+
+    x_move, y_move = x_y_move
+    point.move(x_move, y_move)
+
+    x += x_move
+    y += y_move
+
+    assert (point.x, point.y) == (x, y)
+
+
+@pytest.mark.parametrize(
+    "xs_ys, expected_output, _",
+    point_length,
+    ids=[i[-1] for i in point_length],
+)
+def test_point_length(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    xs_ys: Tuple[int | float],
+    expected_output: int | float,
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    x_one, y_one, x_two, y_two = xs_ys
+    point_one = solution_module.Point(x_one, y_one)
+    point_two = solution_module.Point(x_two, y_two)
+
+    assert (point_one.x, point_one.y) == (x_one, y_one)
+    assert (point_two.x, point_two.y) == (x_two, y_two)
+
+    assert point_one.length(point_two) == expected_output
+    assert point_two.length(point_one) == expected_output
diff --git a/tests/5.1/test_51_c.py b/tests/5.1/test_51_c.py
new file mode 100644
index 00000000..d71881c6
--- /dev/null
+++ b/tests/5.1/test_51_c.py
@@ -0,0 +1,39 @@
+from io import StringIO
+from types import ModuleType
+from typing import Callable
+
+import pytest
+from pytest import MonkeyPatch
+
+from tests import utils
+
+
+def test_red_button_first_open_test(
+    monkeypatch: MonkeyPatch,
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+) -> None:
+    REPEATS = 5
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    first_button = solution_module.RedButton()
+    second_button = solution_module.RedButton()
+
+    assert first_button.count() == 0
+    assert second_button.count() == 0
+
+    mock_print = StringIO()
+    monkeypatch.setattr("sys.stdout", mock_print)
+
+    for time in range(REPEATS):
+        if time % 2 == 0:
+            second_button.click()
+        else:
+            first_button.click()
+
+    printed_output = mock_print.getvalue()
+    excepted_output = "Тревога!\n" * REPEATS
+
+    assert printed_output == excepted_output
+    assert (first_button.count(), second_button.count()) == (2, 3)
diff --git a/tests/5.1/test_51_d.py b/tests/5.1/test_51_d.py
new file mode 100644
index 00000000..886cfffe
--- /dev/null
+++ b/tests/5.1/test_51_d.py
@@ -0,0 +1,40 @@
+from io import StringIO
+from types import ModuleType
+from typing import Callable, Tuple
+
+import pytest
+from pytest import MonkeyPatch
+
+from tests import utils
+from tests.data.test_data_51 import d_test_data
+
+
+@pytest.mark.parametrize(
+    "name_grade, all_hours, excepted_outputs, _",
+    d_test_data,
+    ids=[i[-1] for i in d_test_data],
+)
+def test_programmer(
+    monkeypatch: MonkeyPatch,
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    name_grade: Tuple[str],
+    all_hours: Tuple[int],
+    excepted_outputs: Tuple[str],
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    mock_print = StringIO()
+    monkeypatch.setattr("sys.stdout", mock_print)
+
+    programmer = solution_module.Programmer(*name_grade)
+
+    programmer.work(all_hours[0])
+    assert programmer.info() == excepted_outputs[0]
+
+    for hours, excepted_output in zip(all_hours[1:], excepted_outputs[1:]):
+        programmer.rise()
+        programmer.work(hours)
+        assert programmer.info() == excepted_output
diff --git a/tests/5.1/test_51_e.py b/tests/5.1/test_51_e.py
new file mode 100644
index 00000000..9dc8dfc7
--- /dev/null
+++ b/tests/5.1/test_51_e.py
@@ -0,0 +1,52 @@
+from types import ModuleType
+from typing import Callable, Tuple
+
+import pytest
+
+from tests import utils
+from tests.data.test_data_51 import (rectangle_area_test_data,
+                                     rectangle_perimeter_test_data)
+
+
+@pytest.mark.parametrize(
+    "points, expected_perimeter, _",
+    rectangle_perimeter_test_data,
+    ids=[i[-1] for i in rectangle_perimeter_test_data],
+)
+def test_rectangle_perimeter_test(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    points: Tuple[
+        Tuple[float | int, float | int], Tuple[float | int, float | int]
+    ],
+    expected_perimeter: float | int,
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    rect = solution_module.Rectangle(*points)
+
+    assert rect.perimeter() == expected_perimeter
+
+
+@pytest.mark.parametrize(
+    "points, expected_area, _",
+    rectangle_area_test_data,
+    ids=[i[-1] for i in rectangle_area_test_data],
+)
+def test_rectangle_area_test(
+    load_module: Callable[[str], ModuleType],
+    request: pytest.FixtureRequest,
+    points: Tuple[
+        Tuple[float | int, float | int], Tuple[float | int, float | int]
+    ],
+    expected_area: float | int,
+    _: str,
+) -> None:
+    file_path, _ = utils.get_tested_file_details(request)
+    solution_module = load_module(file_path)
+
+    rect = solution_module.Rectangle(*points)
+
+    assert rect.area() == expected_area
diff --git a/tests/data/test_data_51.py b/tests/data/test_data_51.py
new file mode 100644
index 00000000..c7d3b6b7
--- /dev/null
+++ b/tests/data/test_data_51.py
@@ -0,0 +1,94 @@
+point_init_test_data = [
+    (3, 5, "first open test"),
+    (2, -7, "second open test"),
+    (0.2, -0.7, "floats"),
+    (0, 1, "zero one"),
+    (1, 0, "one zero"),
+    (1000000000000, 99999999999999999, "big numbers"),
+    (0.000000000001, 0.0000000000000009, "small numbers"),
+]
+
+point_move_test_data = [
+    ((3, 5), (2, -3), "first open test"),
+    ((-3, -9), (-12, -5), "negative numbers"),
+    ((-3, -9), (0, 0), "negative numbers zero move"),
+    ((3, 9), (0, 0), "positive number zero move"),
+    ((-3.1, -1.9), (-1.2, -0.5), "float negative float"),
+    ((-6.3, -2.9), (0.0, 0.0), "negative float zero move"),
+    ((3.9, 6.9), (0.0, 0.0), "positive float zero move"),
+]
+
+point_length = [
+    ((2, -7, 7, 9), 16.76, "second open test"),
+    ((2, 7, -7, -9), 18.36, "positive negative"),
+    ((-6, -5, 73, 2), 79.31, "negative positive"),
+    ((-2, -7, -7, -9), 5.39, "all negatives"),
+    ((0, 1, 2, 3), 2.83, "first zero"),
+    ((1, 0, 2, 3), 3.16, "second zero"),
+    ((2, 1, 0, 3), 2.83, "third zero"),
+    ((3, 1, 2, 0), 1.41, "last zero"),
+    ((3, 1, 3, 1), 0.0, "the same points"),
+]
+
+d_test_data = [
+    (
+        ("Васильев Иван", "Junior"),
+        (750, 500, 250, 250),
+        (
+            "Васильев Иван 750ч. 7500тгр.",
+            "Васильев Иван 1250ч. 15000тгр.",
+            "Васильев Иван 1500ч. 20000тгр.",
+            "Васильев Иван 1750ч. 25250тгр.",
+        ),
+        "first open test",
+    ),
+    (
+        ("Some Guy", "Middle"),
+        (100, 500, 250, 250),
+        (
+            "Some Guy 100ч. 1500тгр.",
+            "Some Guy 600ч. 11500тгр.",
+            "Some Guy 850ч. 16750тгр.",
+            "Some Guy 1100ч. 22250тгр.",
+        ),
+        "Middle",
+    ),
+    (
+        ("A Very Senior Guy", "Senior"),
+        (100, 134, 321),
+        (
+            "A Very Senior Guy 100ч. 2000тгр.",
+            "A Very Senior Guy 234ч. 4814тгр.",
+            "A Very Senior Guy 555ч. 11876тгр.",
+        ),
+        "Senior",
+    ),
+    (
+        ("A Very Lazy Senior Guy", "Senior"),
+        (0, 0, 0, 1),
+        (
+            "A Very Lazy Senior Guy 0ч. 0тгр.",
+            "A Very Lazy Senior Guy 0ч. 0тгр.",
+            "A Very Lazy Senior Guy 0ч. 0тгр.",
+            "A Very Lazy Senior Guy 1ч. 23тгр.",
+        ),
+        "Lazy Senior",
+    ),
+]
+
+rectangle_perimeter_test_data = [
+    (((3.2, -4.3), (7.52, 3.14)), 23.52, "first open test"),
+    (((-3.2, -4.3), (-7.52, -3.14)), 10.96, "negative points"),
+    (((1, 2), (3, 4)), 8, "positive points"),
+    (((0.01, 0.02), (0.03, 0.04)), 0.08, "small rectangle"),
+    (((5.0291, 6.78321), (6.49559, 3.59558)), 9.31, "tricky round"),
+]
+
+rectangle_area_test_data = [
+    (((7.52, -4.3), (3.2, 3.14)), 32.14, "second open test"),
+    (((-3.2, -4.3), (-7.52, -3.14)), 5.01, "negative points"),
+    (((0, -4.3), (-7.52, -3.14)), 8.72, "negative points and zero"),
+    (((0.01, 0.02), (0.03, 0.04)), 0.00, "small rectangle"),
+    (((0.1, 0.2), (0.3, 0.4)), 0.04, "a bit bigger rectangle"),
+    (((5.0291, 6.78321), (6.49559, 3.59558)), 4.67, "tricky round"),
+]