Repository: lk-geimfari/mimesis

Issue:
Add decimal generator to Numbers
It's pretty clear what to do from the title of this issue — we need to add a new method which will generate decimal numbers.


Discussion:

Resolving patch:
diff --git a/mimesis/enums.py b/mimesis/enums.py
--- a/mimesis/enums.py
+++ b/mimesis/enums.py
@@ -227,3 +227,4 @@ class NumTypes(Enum):
     FLOATS = 'floats'
     INTEGERS = 'integers'
     COMPLEXES = 'complexes'
+    DECIMALS = 'decimals'
diff --git a/mimesis/providers/numbers.py b/mimesis/providers/numbers.py
--- a/mimesis/providers/numbers.py
+++ b/mimesis/providers/numbers.py
@@ -2,7 +2,8 @@
 
 """Provides data related to numbers."""
 
-from typing import Callable, List, Union
+from decimal import Decimal
+from typing import List
 
 from mimesis.enums import NumTypes
 from mimesis.providers.base import BaseProvider
@@ -18,17 +19,39 @@ class Meta:
 
         name = 'numbers'
 
-    def floats(self, start: float = 0, end: float = 1, n: int = 10,
-               rounding: int = 15) -> List[float]:
+    def floating(self, start: float = -1000.0,
+                 end: float = 1000.0, precision: int = 15) -> float:
+        """Generate random float number in range [start, end].
+
+        :param start: Start range.
+        :param end:  End range.
+        :param precision: Round a number to a given
+            precision in decimal digits, default is 15.
+        :return: Float.
+        """
+        return self.random.uniform(start, end, precision)
+
+    def floats(self, start: float = 0, end: float = 1,
+               n: int = 10, precision: int = 15) -> List[float]:
         """Generate a list of random float numbers.
 
         :param start: Start range.
         :param end: End range.
         :param n: Length of the list.
-        :param rounding: Max number of decimal digits.
+        :param precision: Round a number to a given
+            precision in decimal digits, default is 15.
         :return: The list of floating-point numbers.
         """
-        return [self.random.uniform(start, end, rounding) for _ in range(n)]
+        return [self.floating(start, end, precision) for _ in range(n)]
+
+    def integer(self, start: int = -1000, end: int = 1000) -> int:
+        """Generate random integer from start to end.
+
+        :param start: Start range.
+        :param end: End range.
+        :return: Integer.
+        """
+        return self.random.randint(start, end)
 
     def integers(self, start: int = 0, end: int = 10,
                  n: int = 10) -> List[int]:
@@ -47,9 +70,31 @@ def integers(self, start: int = 0, end: int = 10,
         """
         return self.random.randints(n, start, end)
 
+    def complex_number(self, start_real: float = 0.0,
+                       end_real: float = 1.0,
+                       start_imag: float = 0.0,
+                       end_imag: float = 1.0,
+                       precision_real: int = 15,
+                       precision_imag: int = 15) -> complex:
+        """Generate random complex number.
+
+        :param start_real: Start real range.
+        :param end_real: End real range.
+        :param start_imag: Start imaginary range.
+        :param end_imag: End imaginary range.
+        :param precision_real:  Round a real part of
+            number to a given precision.
+        :param precision_imag:  Round the imaginary part of
+            number to a given precision.
+        :return: Complex numbers.
+        """
+        real_part = self.random.uniform(start_real, end_real, precision_real)
+        imag_part = self.random.uniform(start_imag, end_imag, precision_imag)
+        return complex(real_part, imag_part)
+
     def complexes(self, start_real: float = 0, end_real: float = 1,
                   start_imag: float = 0, end_imag: float = 1,
-                  rounding_real: int = 15, rounding_imag: int = 15,
+                  precision_real: int = 15, precision_imag: int = 15,
                   n: int = 10) -> List[complex]:
         """Generate a list of random complex numbers.
 
@@ -57,82 +102,63 @@ def complexes(self, start_real: float = 0, end_real: float = 1,
         :param end_real: End real range.
         :param start_imag: Start imaginary range.
         :param end_imag: End imaginary range.
-        :param rounding_real: Rounding real part.
-        :param rounding_imag: Roungind imaginary part.
+        :param precision_real:  Round a real part of
+            number to a given precision.
+        :param precision_imag:  Round the imaginary part of
+            number to a given precision.
         :param n: Length of the list.
         :return: A list of random complex numbers.
         """
-        return [
-            complex(self.random.uniform(start_real, end_real, rounding_real),
-                    self.random.uniform(start_imag, end_imag, rounding_imag))
-            for _ in range(n)]
+        numbers = []
+        for _ in range(n):
+            numbers.append(
+                self.complex_number(
+                    start_real=start_real,
+                    end_real=end_real,
+                    start_imag=start_imag,
+                    end_imag=end_imag,
+                    precision_real=precision_real,
+                    precision_imag=precision_imag,
+                ),
+            )
+        return numbers
+
+    def decimal(self, start: float = -1000.0, end: float = 1000.0) -> Decimal:
+        """Generate random decimal number.
+
+        :param start:  Start range.
+        :param end: End range.
+        :return: Decimal object.
+        """
+        return Decimal.from_float(self.floating(start, end))
+
+    def decimals(self, start: float = 0.0,
+                 end: float = 1000.0, n: int = 10) -> List[Decimal]:
+        """Generate decimal number as Decimal objects
+
+        :param start: Start range.
+        :param end: End range.
+        :param n: Length of the list.
+        :return: A list of random decimal numbers.
+        """
+        return [self.decimal(start, end) for _ in range(n)]
 
     def matrix(self, m: int = 10, n: int = 10,
                num_type: NumTypes = NumTypes.FLOATS, **kwargs) -> List[List]:
-        """Generate a m x n matrix with random numbers.
+        """Generate m x n matrix with random numbers.
+
+        This method works with variety of types,
+        so you can pass method-specific **kwargs.
+
+        See code for more details.
 
         :param m: Number of rows.
         :param n: Number of columns.
         :param num_type: NumTypes enum object.
-        :param **kwargs: Other specific arguments.
+        :param kwargs: Other method-specific arguments.
         :return: A matrix of random numbers.
         """
         key = self._validate_enum(num_type, NumTypes)
         kwargs.update({'n': n})
         method = getattr(self, key)
         return [method(**kwargs) for _ in range(m)]
-
-    @staticmethod
-    def primes(start: int = 1, end: int = 999) -> List[int]:
-        """Generate a list of prime numbers.
-
-        :param start: First value of range.
-        :param end: Last value of range.
-        :return: A list of prime numbers from start to end.
-        """
-        # TODO: It should generate random primes with passed length.
-        sieve_size = (end // 2 - 1) if end % 2 == 0 else (end // 2)
-        sieve = [True] * sieve_size
-
-        primes = []  # list of primes
-        # add 2 to the list if it's in the given range
-        if end >= 2:
-            primes.append(2)
-        for i in range(sieve_size):
-            if sieve[i]:
-                value_at_i = i * 2 + 3
-                primes.append(value_at_i)
-                for j in range(i, sieve_size, value_at_i):
-                    sieve[j] = False
-
-        chop_index = 0
-        for i in range(len(primes)):
-            if primes[i] >= start:
-                chop_index = i
-                break
-        return primes[chop_index:]
-
-    def digit(self, to_bin: bool = False) -> Union[str, int]:
-        """Get a random digit.
-
-        :param to_bin: If True then convert to binary.
-        :return: Digit.
-
-        :Example:
-            4.
-        """
-        digit = self.random.randint(0, 9)
-
-        if to_bin:
-            return bin(digit)
-
-        return digit
-
-    def between(self, minimum: int = 1, maximum: int = 1000) -> int:
-        """Generate a random number between minimum and maximum.
-
-        :param minimum: Minimum of range.
-        :param maximum: Maximum of range.
-        :return: Number.
-        """
-        return self.random.randint(minimum, maximum)


Test patch:
diff --git a/tests/test_providers/test_generic.py b/tests/test_providers/test_generic.py
--- a/tests/test_providers/test_generic.py
+++ b/tests/test_providers/test_generic.py
@@ -160,7 +160,6 @@ def test_generic_internet(self, g1, g2):
 
     def test_generic_numbers(self, g1, g2):
         assert g1.numbers.integers() == g2.numbers.integers()
-        assert g1.numbers.digit() == g2.numbers.digit()
 
     def test_generic_path(self, g1, g2):
         assert g1.path.root() == g2.path.root()
diff --git a/tests/test_providers/test_numbers.py b/tests/test_providers/test_numbers.py
--- a/tests/test_providers/test_numbers.py
+++ b/tests/test_providers/test_numbers.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import decimal
 import re
 
 import pytest
@@ -36,7 +37,7 @@ def test_floats(self, numbers, start, end):
         result = numbers.floats(n=1000)
         assert len(result) == 1000
 
-        result = numbers.floats(rounding=4)
+        result = numbers.floats(precision=4)
         for e in result:
             assert len(str(e).split('.')[1]) <= 4
 
@@ -57,6 +58,23 @@ def test_integers(self, numbers, start, end):
         element = numbers.random.choice(result)
         assert isinstance(element, int)
 
+    @pytest.mark.parametrize(
+        'start, end', [
+            (1, 10),
+            (10, 20),
+            (20, 30),
+        ],
+    )
+    def test_decimals(self, numbers, start, end):
+        result = numbers.decimals(start=start, end=end)
+
+        assert max(result) <= end
+        assert min(result) >= start
+        assert isinstance(result, list)
+
+        element = numbers.random.choice(result)
+        assert isinstance(element, decimal.Decimal)
+
     @pytest.mark.parametrize(
         'start_real, end_real, start_imag, end_imag', [
             (1.2, 10, 1, 2.4),
@@ -65,9 +83,9 @@ def test_integers(self, numbers, start, end):
         ],
     )
     def test_complexes(self, numbers,
-                         start_real, end_real, start_imag, end_imag):
+                       start_real, end_real, start_imag, end_imag):
         result = numbers.complexes(start_real, end_real,
-                                     start_imag, end_imag)
+                                   start_imag, end_imag)
         assert max(e.real for e in result) <= end_real
         assert min(e.real for e in result) >= start_real
         assert max(e.imag for e in result) <= end_imag
@@ -78,17 +96,38 @@ def test_complexes(self, numbers,
         result = numbers.complexes(n=1000)
         assert len(result) == 1000
 
-        result = numbers.complexes(rounding_real=4, rounding_imag=6)
+        result = numbers.complexes(precision_real=4, precision_imag=6)
         for e in result:
             assert len(str(e.real).split('.')[1]) <= 4
             assert len(str(e.imag).split('.')[1]) <= 6
 
+    @pytest.mark.parametrize(
+        'sr, er, si, ei, pr, pi', [
+            (1.2, 10, 1, 2.4, 15, 15),
+            (10.4, 20.0, 2.3, 10, 10, 10),
+            (20.3, 30.8, 2.4, 4.5, 12, 12),
+        ],
+    )
+    def test_complex_number(self, numbers, sr, er, si, ei, pr, pi):
+        result = numbers.complex_number(
+            start_real=sr,
+            end_real=er,
+            start_imag=si,
+            end_imag=ei,
+            precision_real=pr,
+            precision_imag=pi,
+        )
+        assert isinstance(result, complex)
+        assert len(str(result.real).split('.')[1]) <= pr
+        assert len(str(result.imag).split('.')[1]) <= pi
+
     def test_matrix(self, numbers):
+        # TODO: Rewrite it to cover all cases
 
         with pytest.raises(NonEnumerableError):
             numbers.matrix(num_type='int')
 
-        result = numbers.matrix(rounding=4)
+        result = numbers.matrix(precision=4)
         assert len(result) == 10
         for row in result:
             assert len(row) == 10
@@ -105,7 +144,7 @@ def test_matrix(self, numbers):
                 assert isinstance(e, int)
 
         result = numbers.matrix(
-            num_type=NumTypes.COMPLEXES, rounding_real=4, rounding_imag=6)
+            num_type=NumTypes.COMPLEXES, precision_real=4, precision_imag=6)
         assert len(result) == 10
         for row in result:
             assert len(row) == 10
@@ -113,35 +152,21 @@ def test_matrix(self, numbers):
                 assert len(str(e.real).split('.')[1]) <= 4
                 assert len(str(e.imag).split('.')[1]) <= 6
 
-    def test_primes(self, numbers):
-        result = numbers.primes()
-        assert len(result) == 168
-        assert isinstance(result, list)
+    def test_integer(self, numbers):
+        result = numbers.integer(-100, 100)
+        assert isinstance(result, int)
+        assert -100 <= result <= 100
 
-        result = numbers.primes(500, 500000)
-        assert len(result) == 41443
-        assert isinstance(result, list)
+    def test_float(self, numbers):
+        result = numbers.floating(-100, 100, precision=15)
+        assert isinstance(result, float)
+        assert -100 <= result <= 100
+        assert len(str(result).split('.')[1]) <= 15
 
-        result = numbers.primes(start=10, end=200)[1]
-        assert result == 13
-
-    def test_digit(self, numbers):
-        digits = (
-            0, 1, 2,
-            3, 4, 5,
-            6, 7, 8,
-            9,
-        )
-        result = numbers.digit()
-        assert result in digits
-
-        result = numbers.digit(to_bin=True)
-        assert isinstance(result, str)
-
-    def test_between(self, numbers):
-        result = numbers.between(90, 100)
-        assert result >= 90
-        assert result <= 100
+    def test_decimal(self, numbers):
+        result = numbers.decimal(-100, 100)
+        assert -100 <= result <= 100
+        assert isinstance(result, decimal.Decimal)
 
 
 class TestSeededNumbers(object):
@@ -158,10 +183,14 @@ def test_floats(self, n1, n2):
         assert n1.floats() == n2.floats()
         assert n1.floats(n=5) == n2.floats(n=5)
 
+    def test_decimals(self, n1, n2):
+        assert n1.decimals() == n2.decimals()
+        assert n1.decimals(n=5) == n2.decimals(n=5)
+
     def test_integers(self, n1, n2):
         assert n1.integers() == n2.integers()
         assert n1.integers(start=-999, end=999, n=10) == \
-            n2.integers(start=-999, end=999, n=10)
+               n2.integers(start=-999, end=999, n=10)
 
     def test_complexes(self, n1, n2):
         assert n1.complexes() == n2.complexes()
@@ -171,15 +200,14 @@ def test_matrix(self, n1, n2):
         assert n1.matrix() == n2.matrix()
         assert n1.matrix(n=5) == n2.matrix(n=5)
 
-    def test_digit(self, n1, n2):
-        assert n1.digit() == n2.digit()
-        assert n1.digit(to_bin=True) == n2.digit(to_bin=True)
+    def test_integer(self, n1, n2):
+        assert n1.integer() == n2.integer()
+
+    def test_float(self, n1, n2):
+        assert n1.floating() == n2.floating()
 
-    def test_between(self, n1, n2):
-        assert n1.between() == n2.between()
-        assert n1.between(minimum=42, maximum=2048) == \
-            n2.between(minimum=42, maximum=2048)
+    def test_decimal(self, n1, n2):
+        assert n1.decimal() == n2.decimal()
 
-    @pytest.mark.skip(reason='Method refactoring needed.')
-    def test_primes(self, n1, n2):
-        assert n1.primes() == n2.primes()
+    def test_complex_number(self, n1, n2):
+        assert n1.complex_number() == n2.complex_number()