Repository: joke2k/faker
Pull request #1908 (2023-09-08 17:04:06)

fix: pyfloat type error on none max value
### What does this change
Handle the combinations of specified digits and min and max values in a more advanced way for pyfloat.

### What was wrong

There were a few issues that this PR aims to address:
- This line was throwing a TypeError if max_value or min_value was None
```python
self.generator.random.uniform(0, max_value - min_value)
```

- This line would throw an error when `needed_left_digits == sys.float_info.dig` because the range was then invalid
- Giving in `-(10**sys.float_info.dig + 1)` as max value and `10**sys.float_info.dig` as min_value can never succeed because of the following code at the end:
```python
        if right_digits:
            result = min(result, 10**left_digits - float(f'0.{"0" * (right_digits - 1)}1'))
            result = max(result, -(10**left_digits + float(f'0.{"0" * (right_digits - 1)}1')))
        else:
            result = min(result, 10**left_digits - 1)
            result = max(result, -(10**left_digits + 1))
```
- The number of left and right digits were not always respected.

### How this fixes it
- `ValueError` is raised if max_value or min_value is out of bounds
- right digits is set to 0 if the inferred left digits is already equals to sys.float_info.dig
- Rather than choosing only the `left_number` which then caused issues in trying to figure out a "right number" which could then end up being lower/higher than the configured max/min value, an int is chosen randomly between min and max values which are scaled up to have the length of left + right digits so that afterwards a `.` can be inserted in the right place.
- The previous adjustment code containing the TypeError is removed.

Fixes https://github.com/joke2k/faker/issues/1907



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index d6778af2..5b3d321f 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 19.5.0
+current_version = 19.6.1
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/.github/workflows/autobump.yml b/.github/workflows/autobump.yml
index a9b333d9..fbf5a590 100644
--- a/.github/workflows/autobump.yml
+++ b/.github/workflows/autobump.yml
@@ -27,7 +27,7 @@ jobs:
           minor: "bump-version:minor"
           defaultPart: "patch"
           githubToken: ${{ secrets.GITHUB_TOKEN }}
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
         with:
          fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
       - name: Get Next Version
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0239a0a5..9a59c1fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -40,7 +40,7 @@ jobs:
   checkmanifest:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -64,7 +64,7 @@ jobs:
   isort:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -89,7 +89,7 @@ jobs:
   black:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -113,7 +113,7 @@ jobs:
   doc8:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -141,7 +141,7 @@ jobs:
         python: ["3.8", "3.9", "3.10", "3.11"]
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
@@ -171,7 +171,7 @@ jobs:
         python: ["3.8", "3.9", "3.10", "3.11", "pypy-3.9"]
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - name: Setup pip cache
         uses: actions/cache@v3
         id: pipcache
@@ -213,7 +213,7 @@ jobs:
         arch: ["x86", "x64"]
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - name: Setup pip cache
         uses: actions/cache@v3
         id: pipcache
@@ -244,7 +244,7 @@ jobs:
     needs: [flake8, isort, black, doc8, checkmanifest, typing]
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Setup pip cache
       uses: actions/cache@v3
       id: pipcache
diff --git a/.github/workflows/gh-release.yml b/.github/workflows/gh-release.yml
index 45f8242b..bee1bc38 100644
--- a/.github/workflows/gh-release.yml
+++ b/.github/workflows/gh-release.yml
@@ -17,7 +17,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout code
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
       - name: Create Release
         id: create_release
         uses: actions/create-release@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6c0cc427..9bcf1cc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 ## Changelog
 
+### [v19.6.1 - 2023-09-11](https://github.com/joke2k/faker/compare/v19.6.0...v19.6.1)
+
+* Bump actions/checkout from 3 to 4 (#1909). Thanks @dependabot[bot].
+
+### [v19.6.0 - 2023-09-08](https://github.com/joke2k/faker/compare/v19.5.0...v19.6.0)
+
+* Add addresses for `en_BD`. Thanks @aamibhoot.
+
 ### [v19.5.0 - 2023-09-08](https://github.com/joke2k/faker/compare/v19.4.0...v19.5.0)
 
 * Add `color_rgb`, `color_rgb_float`, `color_hsv` and `color_hsl`. Thanks @fdemmer.
diff --git a/Makefile b/Makefile
index e9c08af1..56ccf054 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,9 @@
 test:
 	tox -e py
 
+flake8:
+	flake8 --extend-ignore=E203 faker tests
+
 mypy:
 	mypy --install-types --non-interactive --config mypy.ini faker
 
@@ -10,7 +13,7 @@ black:
 isort:
 	isort --atomic .
 
-lint: isort black mypy
+lint: isort black mypy flake8
 
 release:
 	check-manifest
diff --git a/VERSION b/VERSION
index 912dbfae..ab78bc6d 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-19.5.0
+19.6.1
diff --git a/docs/conf.py b/docs/conf.py
index 75a865cc..1d1920a0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = "2014, Daniele Faraglia"
 # built documents.
 #
 # The short X.Y version.
-version = "19.5.0"
+version = "19.6.1"
 # The full version, including alpha/beta/rc tags.
-release = "19.5.0"
+release = "19.6.1"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/faker/__init__.py b/faker/__init__.py
index 2ee4cf4c..5b4e5e76 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,6 +2,6 @@ from faker.factory import Factory
 from faker.generator import Generator
 from faker.proxy import Faker
 
-VERSION = "19.5.0"
+VERSION = "19.6.1"
 
 __all__ = ("Factory", "Generator", "Faker")
diff --git a/faker/providers/__init__.py b/faker/providers/__init__.py
index ff1b0a1b..add840a7 100644
--- a/faker/providers/__init__.py
+++ b/faker/providers/__init__.py
@@ -97,6 +97,7 @@ class BaseProvider:
         "en": (
             "AG",
             "AU",
+            "BD",
             "BW",
             "CA",
             "DK",
diff --git a/faker/providers/address/en_BD/__init__.py b/faker/providers/address/en_BD/__init__.py
new file mode 100644
index 00000000..1fd4ca52
--- /dev/null
+++ b/faker/providers/address/en_BD/__init__.py
@@ -0,0 +1,547 @@
+"""
+    Contributed by: @aamibhoot 🇧🇩
+"""
+
+from .. import Provider as AddressProvider
+
+
+class Provider(AddressProvider):
+    area_names = (
+        "Ali",
+        "Alam",
+        "Abhay",
+        "Anwar",
+        "Brahmin",
+        "Botia",
+        "Baghar",
+        "Begum",
+        "Bijoy",
+        "Bandar",
+        "Balia",
+        "Bajit",
+        "Baker",
+        "Borhan",
+        "Bakhsh",
+        "Badr",
+        "Biram",
+        "Biswnath",
+        "Chouddah",
+        "Chital",
+        "Daud",
+        "Daulat",
+        "Dev",
+        "Devi",
+        "Islam",
+        "Ful",
+        "Fakir",
+        "Fatik",
+        "Gopal",
+        "Gour",
+        "Haji",
+        "Hariram",
+        "Hossain",
+        "Hakim",
+        "Jibon",
+        "Jagannath",
+        "Kumar",
+        "Kali",
+        "Keshav",
+        "Qutub",
+        "Kabi",
+        "Kalia",
+        "Karim",
+        "Kazi",
+        "Kamal",
+        "Lal",
+        "Murad",
+        "Manohar",
+        "Mir",
+        "Mahes",
+        "Moral",
+        "Molla",
+        "Mohammad",
+        "Maniram",
+        "Manik",
+        "Mirza",
+        "Mud",
+        "Mohan",
+        "Mahadev",
+        "Madhab",
+        "Nasir",
+        "Naria",
+        "Nazir",
+        "Nalitha",
+        "Nandi",
+        "Osmani",
+        "Pai",
+        "Palash",
+        "Parvati",
+        "Ram",
+        "Ray",
+        "Rani",
+        "Sona",
+        "Sharan",
+        "Shyam",
+        "Subarna",
+        "Siraj",
+        "Sakhi",
+        "Sadar",
+        "Sundar",
+        "Syed",
+        "Shahjahan",
+        "Shanti",
+        "Shib",
+        "Ter",
+        "Tara",
+        "Uzir",
+    )
+
+    building_names = (
+        "House No.",
+        "Building No.",
+        "House No.",
+        "Holding No.",
+    )
+
+    building_number_formats = ("%", "%#", "%##")
+
+    city_prefixes = ("North", "East", "West", "South", "Middle", "New", "Old")
+
+    city_suffixes = (
+        "Bazar",
+        "Bari",
+        "Char",
+        "Diya",
+        "Danga",
+        "Ganz",
+        "Gram",
+        "Gan",
+        "Gan",
+        "Garh",
+        "Hat",
+        "Har",
+        "Khali",
+        "Mati",
+        "Nagar",
+        "Pur",
+        "Tala",
+    )
+
+    cities = (
+        "Barguna",
+        "Barisal",
+        "Bhola",
+        "Bandarban",
+        "Brahmanbaria",
+        "Bagherhat",
+        "Bogura",
+        "Chandpur",
+        "Chittagong",
+        "Cumilla",
+        "Cox's Bazar",
+        "Chuadanga",
+        "Dhaka",
+        "Dinajpur",
+        "Faripur",
+        "Feni",
+        "Gazipur",
+        "Gopalganj",
+        "Gaibandha",
+        "Habiganj",
+        "Jhalokati",
+        "Jessore",
+        "Jhenaidah",
+        "Jamalpur",
+        "Joypurhat",
+        "Khagrachhari",
+        "Kishoreganj",
+        "Khulna",
+        "Kushtia",
+        "Kurigram",
+        "Lakshmipur",
+        "Lalmonirhat",
+        "Madaripur",
+        "Manikganj",
+        "Munshiganj",
+        "Magura",
+        "Meherpur",
+        "Mymensingh",
+        "Maulvibazar",
+        "Noakhali",
+        "Narayanganj",
+        "Narsingdi",
+        "Narail",
+        "Netrokona",
+        "Naogaon",
+        "Naogaon",
+        "Chapainawabganj",
+        "Nilphamari",
+        "Patuakhali",
+        "Pirojpur",
+        "Pabna",
+        "Panchagarh",
+        "Rangpur",
+        "Shariatpur",
+        "Satkhira",
+        "Sherpur",
+        "Sirajganj",
+        "Sunamganj",
+        "Sylhet",
+        "Tangail",
+        "Thakurgaon",
+    )
+
+    countries = (
+        "Afghanistan",
+        "Albania",
+        "Algeria",
+        "American Samoa",
+        "Andorra",
+        "Angola",
+        "Anguilla",
+        "Antarctica (the territory South of 60 deg S)",
+        "Antigua and Barbuda",
+        "Argentina",
+        "Armenia",
+        "Aruba",
+        "Australia",
+        "Austria",
+        "Azerbaijan",
+        "Bahamas",
+        "Bahrain",
+        "Bangladesh",
+        "Barbados",
+        "Belarus",
+        "Belgium",
+        "Belize",
+        "Benin",
+        "Bermuda",
+        "Bhutan",
+        "Bolivia",
+        "Bosnia and Herzegovina",
+        "Botswana",
+        "Bouvet Island (Bouvetoya)",
+        "Brazil",
+        "British Indian Ocean Territory (Chagos Archipelago)",
+        "British Virgin Islands",
+        "Brunei Darussalam",
+        "Bulgaria",
+        "Burkina Faso",
+        "Burundi",
+        "Cambodia",
+        "Cameroon",
+        "Canada",
+        "Cape Verde",
+        "Cayman Islands",
+        "Central African Republic",
+        "Chad",
+        "Chile",
+        "China",
+        "Christmas Island",
+        "Cocos (Keeling) Islands",
+        "Colombia",
+        "Comoros",
+        "Congo",
+        "Congo",
+        "Cook Islands",
+        "Costa Rica",
+        "Cote d'Ivoire",
+        "Croatia",
+        "Cuba",
+        "Cyprus",
+        "Czech Republic",
+        "Denmark",
+        "Djibouti",
+        "Dominica",
+        "Dominican Republic",
+        "Ecuador",
+        "Egypt",
+        "El Salvador",
+        "Equatorial Guinea",
+        "Eritrea",
+        "Estonia",
+        "Ethiopia",
+        "Faroe Islands",
+        "Falkland Islands (Malvinas)",
+        "Fiji",
+        "Finland",
+        "France",
+        "French Guiana",
+        "French Polynesia",
+        "French Southern Territories",
+        "Gabon",
+        "Gambia",
+        "Georgia",
+        "Germany",
+        "Ghana",
+        "Gibraltar",
+        "Greece",
+        "Greenland",
+        "Grenada",
+        "Guadeloupe",
+        "Guam",
+        "Guatemala",
+        "Guernsey",
+        "Guinea",
+        "Guinea-Bissau",
+        "Guyana",
+        "Haiti",
+        "Heard Island and McDonald Islands",
+        "Holy See (Vatican City State)",
+        "Honduras",
+        "Hong Kong",
+        "Hungary",
+        "Iceland",
+        "India",
+        "Indonesia",
+        "Iran",
+        "Iraq",
+        "Ireland",
+        "Isle of Man",
+        "Israel",
+        "Italy",
+        "Jamaica",
+        "Japan",
+        "Jersey",
+        "Jordan",
+        "Kazakhstan",
+        "Kenya",
+        "Kiribati",
+        "Korea",
+        "Korea",
+        "Kuwait",
+        "Kyrgyz Republic",
+        "Lao People's Democratic Republic",
+        "Latvia",
+        "Lebanon",
+        "Lesotho",
+        "Liberia",
+        "Libyan Arab Jamahiriya",
+        "Liechtenstein",
+        "Lithuania",
+        "Luxembourg",
+        "Macao",
+        "Madagascar",
+        "Malawi",
+        "Malaysia",
+        "Maldives",
+        "Mali",
+        "Malta",
+        "Marshall Islands",
+        "Martinique",
+        "Mauritania",
+        "Mauritius",
+        "Mayotte",
+        "Mexico",
+        "Micronesia",
+        "Moldova",
+        "Monaco",
+        "Mongolia",
+        "Montenegro",
+        "Montserrat",
+        "Morocco",
+        "Mozambique",
+        "Myanmar",
+        "Namibia",
+        "Nauru",
+        "Nepal",
+        "Netherlands Antilles",
+        "Netherlands",
+        "New Caledonia",
+        "New Zealand",
+        "Nicaragua",
+        "Niger",
+        "Nigeria",
+        "Niue",
+        "Norfolk Island",
+        "North Macedonia",
+        "Northern Mariana Islands",
+        "Norway",
+        "Oman",
+        "Pakistan",
+        "Palau",
+        "Palestinian Territory",
+        "Panama",
+        "Papua New Guinea",
+        "Paraguay",
+        "Peru",
+        "Philippines",
+        "Pitcairn Islands",
+        "Poland",
+        "Portugal",
+        "Puerto Rico",
+        "Qatar",
+        "Reunion",
+        "Romania",
+        "Russian Federation",
+        "Rwanda",
+        "Saint Barthelemy",
+        "Saint Helena",
+        "Saint Kitts and Nevis",
+        "Saint Lucia",
+        "Saint Martin",
+        "Saint Pierre and Miquelon",
+        "Saint Vincent and the Grenadines",
+        "Samoa",
+        "San Marino",
+        "Sao Tome and Principe",
+        "Saudi Arabia",
+        "Senegal",
+        "Serbia",
+        "Seychelles",
+        "Sierra Leone",
+        "Singapore",
+        "Slovakia (Slovak Republic)",
+        "Slovenia",
+        "Solomon Islands",
+        "Somalia",
+        "South Africa",
+        "South Georgia and the South Sandwich Islands",
+        "Spain",
+        "Sri Lanka",
+        "Sudan",
+        "Suriname",
+        "Svalbard & Jan Mayen Islands",
+        "Swaziland",
+        "Sweden",
+        "Switzerland",
+        "Syrian Arab Republic",
+        "Taiwan",
+        "Tajikistan",
+        "Tanzania",
+        "Thailand",
+        "Timor-Leste",
+        "Togo",
+        "Tokelau",
+        "Tonga",
+        "Trinidad and Tobago",
+        "Tunisia",
+        "Turkey",
+        "Turkmenistan",
+        "Turks and Caicos Islands",
+        "Tuvalu",
+        "Uganda",
+        "Ukraine",
+        "United Arab Emirates",
+        "United Kingdom",
+        "United States of America",
+        "United States Minor Outlying Islands",
+        "United States Virgin Islands",
+        "Uruguay",
+        "Uzbekistan",
+        "Vanuatu",
+        "Venezuela",
+        "Vietnam",
+        "Wallis and Futuna",
+        "Western Sahara",
+        "Yemen",
+        "Zambia",
+        "Zimbabwe",
+    )
+
+    secondary_address_formats = (
+        "Flat %",
+        "Flat %#",
+        "Studio %",
+        "Studio %#",
+        "Apartment %",
+        "Apartment %#",
+    )
+
+    street_suffixes = (
+        "Avenue",
+        "Center",
+        "Square",
+        "Lane",
+        "Ghat",
+        "Corner",
+        "Lane",
+        "Highway",
+        "Mohalla",
+        "Moor",
+        "Para",
+        "Park",
+        "Plaza",
+        "Road",
+        "Road",
+        "Sorok",
+        "Station",
+        "Stand",
+    )
+
+    postcode_formats = ("%###",)
+    street_name_formats = (
+        "{{area_name}}{{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{city_suffix}}",
+        "{{area_name}}{{city_suffix}}",
+        "{{area_name}}{{city_suffix}} {{street_suffix}}",
+        "{{city_prefix}} {{area_name}}{{city_suffix}} {{street_suffix}}",
+    )
+    street_address_formats = (
+        "{{building_name}} {{building_number}}, {{street_name}}",
+        "{{secondary_address}}, {{building_name}} {{building_number}}, {{street_name}}",
+    )
+    town_formats = ("{{area_name}}{{city_suffix}}",)
+    address_formats = ("{{street_address}}, {{town}}, {{city}}, {{postcode}}",)
+
+    def administrative_unit(self) -> str:
+        """
+        :example: 'Dhaka'
+        """
+        return self.random_element(self.cities)
+
+    def area_name(self) -> str:
+        """
+        :example: 'Dhanmondi'
+        """
+        return self.random_element(self.area_names)
+
+    def building_name(self) -> str:
+        """
+        :example: 'House No.'
+        """
+        return self.random_element(self.building_names)
+
+    def building_number(self) -> str:
+        """
+        :example: '791'
+        """
+        return self.numerify(self.random_element(self.building_number_formats))
+
+    def city_prefix(self) -> str:
+        """
+        :example: 'North'
+        """
+        return self.random_element(self.city_prefixes)
+
+    def city(self) -> str:
+        """
+        :example: 'Dhaka'
+        """
+        return self.random_element(self.cities)
+
+    def postcode(self) -> str:
+        """
+        See
+        https://bdpost.portal.gov.bd/site/page/6aaeabe4-479b-4e5a-a671-e9e5b994bf9a
+        """
+        return self.numerify(self.random_element(self.postcode_formats))
+
+    def secondary_address(self) -> str:
+        """
+        As the generated string format is a Bengali word but English number so splitting the value by space
+        and then convert the English number to Bengali number and concat with generated Bengali word
+        and return
+        : example : 'Apartment 123'
+        """
+        value = self.bothify(self.random_element(self.secondary_address_formats))
+        word_list = value.split(" ")
+        return word_list[0] + " " + word_list[1]
+
+    def town(self) -> str:
+        """
+        :example: 'Dhanmondi'
+        """
+        pattern: str = self.random_element(self.town_formats)
+        return self.generator.parse(pattern)
diff --git a/faker/providers/python/__init__.py b/faker/providers/python/__init__.py
index 3118fb89..f479e34b 100644
--- a/faker/providers/python/__init__.py
+++ b/faker/providers/python/__init__.py
@@ -161,11 +161,21 @@ class Provider(BaseProvider):
             raise ValueError("Max value must fit within left digits")
         if left_digits is not None and min_value and math.ceil(math.log10(abs(min_value))) > left_digits:
             raise ValueError("Min value must fit within left digits")
+        if max_value is not None and max_value < -(10**sys.float_info.dig + 1):
+            raise ValueError(f"Max value must be greater than {-10**sys.float_info.dig - 1}")
+        if min_value is not None and min_value > 10**sys.float_info.dig - 1:
+            raise ValueError(f"Min value must be less than {10**sys.float_info.dig - 1}")
 
+        left_digits_requested = left_digits is not None
+        right_digits_requested = right_digits is not None
         # Make sure at least either left or right is set
         if left_digits is None and right_digits is None:
             needed_left_digits = max(1, math.ceil(math.log10(max(abs(max_value or 1), abs(min_value or 1)))))
-            right_digits = self.random_int(1, sys.float_info.dig - needed_left_digits)
+            if (sys.float_info.dig - needed_left_digits) < 1:
+                right_digits = 0
+            else:
+                right_digits = self.random_int(1, sys.float_info.dig - needed_left_digits)
+
 
         # If only one side is set, choose #digits for other side
         if (left_digits is None) ^ (right_digits is None):
@@ -181,32 +191,67 @@ class Provider(BaseProvider):
                 f"{sys.float_info.dig})",
             )
 
-        sign = ""
         if (min_value is not None) or (max_value is not None):
             # Copy values to ensure we're not modifying the original values and thus going out of bounds
-            left_min_value = min_value
-            left_max_value = max_value
+            temp_min_value = min_value
+            temp_max_value = max_value
             # Make sure left_digits still respected
-            if left_digits is not None:
-                if max_value is None:
-                    left_max_value = 10**left_digits  # minus smallest representable, adjusted later
-                if min_value is None:
-                    left_min_value = -(10**left_digits)  # plus smallest representable, adjusted later
+            if max_value is None:
+                temp_max_value = 10 ** (left_digits + right_digits)
+                if min_value is not None:
+                    temp_min_value = min_value * 10 ** right_digits
+            if min_value is None:
+                temp_min_value = -(10 ** (left_digits + right_digits))
+                if max_value is not None:
+                    if max_value < temp_min_value:
+                        temp_min_value = max_value - 1
+                    temp_max_value = max_value * 10 ** right_digits
 
             if max_value is not None and max_value < 0:
-                left_max_value += 1  # as the random_int will be generated up to max_value - 1
+                temp_max_value += 1  # as the random_int will be generated up to max_value - 1
             if min_value is not None and min_value < 0:
-                left_min_value += 1  # as we then append digits after the left_number
-            left_number = self._safe_random_int(
-                left_min_value,
-                left_max_value,
-                positive,
+                temp_min_value += 1  # as we then append digits after the left_number
+
+            opposite_signs = (temp_min_value * temp_max_value) < 0
+            if opposite_signs and left_digits_requested:
+                positive_number = self._safe_random_int(
+                    10 ** (left_digits + right_digits - 1),
+                    temp_max_value,
+                    positive=True,
+                )
+                negative_number = self._safe_random_int(
+                    temp_min_value,
+                    -(10 ** (left_digits + right_digits - 1)),
+                    positive=False,
+                )
+                number = self.random_element((positive_number, negative_number))
+            else:
+                number = self._safe_random_int(
+                    temp_min_value,
+                    temp_max_value,
+                    positive,
+                )
+
+            params_requested = (
+                    right_digits_requested or left_digits_requested or ((min_value is None) ^ (max_value is None))
             )
+            if params_requested:
+                result = (
+                    float(f"{str(number)[:-right_digits]}.{str(number)[-right_digits:]}")
+                    if right_digits
+                    else float(number)
+                )
+            else:
+                fix_right_digits_len = right_digits > 0
+                result = float(f"{number}.{self.random_number(right_digits, fix_len=fix_right_digits_len)}")
+
         else:
             sign = "+" if positive else self.random_element(("+", "-"))
-            left_number = self.random_number(left_digits)
+            fix_left_digits_len = left_digits > 0
+            left_number = self.random_number(left_digits, fix_len=fix_left_digits_len)
+            fix_right_digits_len = right_digits > 0
+            result = float(f"{sign}{left_number}.{self.random_number(right_digits, fix_len=fix_right_digits_len)}")
 
-        result = float(f"{sign}{left_number}.{self.random_number(right_digits)}")
         if positive and result == 0:
             if right_digits:
                 result = float("0." + "0" * (right_digits - 1) + "1")
@@ -220,17 +265,6 @@ class Provider(BaseProvider):
             result = min(result, 10**left_digits - 1)
             result = max(result, -(10**left_digits + 1))
 
-        # It's possible for the result to end up > than max_value or < than min_value
-        # When this happens we introduce some variance so we're not always the exactly the min_value or max_value.
-        # Which can happen a lot depending on the difference of the values.
-        # Ensure the variance is bound by the difference between the max and min
-        if max_value is not None:
-            if result > max_value:
-                result = result - (result - max_value + self.generator.random.uniform(0, max_value - min_value))
-        if min_value is not None:
-            if result < min_value:
-                result = result + (min_value - result + self.generator.random.uniform(0, max_value - min_value))
-
         return result
 
     def _safe_random_int(self, min_value: float, max_value: float, positive: bool) -> int:


Test patch:
diff --git a/tests/providers/conftest.py b/tests/providers/conftest.py
index 02c599d4..e5ed39a6 100644
--- a/tests/providers/conftest.py
+++ b/tests/providers/conftest.py
@@ -19,7 +19,7 @@ def _class_locale_faker(request):
     match = LOCALE_TEST_CLASS_NAME_REGEX.fullmatch(class_name)
     if not match:
         return None
-    locale = f'{match.group("language")}_{match.group("region")}'
+    locale = f'{match.group("language").lower()}_{match.group("region").upper()}'
     locale = pylocale.normalize(locale).split(".")[0]
     return Faker(locale=locale)
 
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index 35be9a81..fa8ebf29 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -14,6 +14,7 @@ from faker.providers.address.de_CH import Provider as DeChAddressProvider
 from faker.providers.address.de_DE import Provider as DeDeAddressProvider
 from faker.providers.address.el_GR import Provider as ElGrAddressProvider
 from faker.providers.address.en_AU import Provider as EnAuAddressProvider
+from faker.providers.address.en_BD import Provider as EnBdAddressProvider
 from faker.providers.address.en_CA import Provider as EnCaAddressProvider
 from faker.providers.address.en_GB import Provider as EnGbAddressProvider
 from faker.providers.address.en_IE import Provider as EnIeAddressProvider
@@ -411,6 +412,61 @@ class TestEnAu:
             assert state_abbr.isupper()
 
 
+class TestEnBd:
+    """Test en_BD address provider methods"""
+
+    def test_administrative_unit(self, faker, num_samples):
+        for _ in range(num_samples):
+            administrative_unit = faker.administrative_unit()
+            assert isinstance(administrative_unit, str)
+            assert administrative_unit in EnBdAddressProvider.cities
+
+    def test_area_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            area_name = faker.area_name()
+            assert isinstance(area_name, str)
+            assert area_name in EnBdAddressProvider.area_names
+
+    def test_building_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            building_name = faker.building_name()
+            assert isinstance(building_name, str)
+            assert building_name in EnBdAddressProvider.building_names
+
+    def test_building_number(self, faker, num_samples):
+        for _ in range(num_samples):
+            building_number = faker.building_number()
+            assert isinstance(building_number, str)
+
+    def test_city_prefix(self, faker, num_samples):
+        for _ in range(num_samples):
+            city_prefix = faker.city_prefix()
+            assert isinstance(city_prefix, str)
+            assert city_prefix in EnBdAddressProvider.city_prefixes
+
+    def test_city(self, faker, num_samples):
+        for _ in range(num_samples):
+            city = faker.city()
+            assert isinstance(city, str)
+            assert city in EnBdAddressProvider.cities
+
+    def test_postcode(self, faker, num_samples):
+        for _ in range(num_samples):
+            postcode = faker.postcode()
+            assert isinstance(postcode, str)
+            assert re.fullmatch(r"\d{4}", postcode)
+
+    def test_secondary_address(self, faker, num_samples):
+        for _ in range(num_samples):
+            secondary_address = faker.secondary_address()
+            assert isinstance(secondary_address, str)
+
+    def test_town(self, faker, num_samples):
+        for _ in range(num_samples):
+            town = faker.town()
+            assert isinstance(town, str)
+
+
 class TestEnCa:
     """Test en_CA address provider methods"""
 
diff --git a/tests/providers/test_python.py b/tests/providers/test_python.py
index 27a2a69e..d721f7dd 100644
--- a/tests/providers/test_python.py
+++ b/tests/providers/test_python.py
@@ -162,7 +162,7 @@ class TestPyfloat(unittest.TestCase):
         self.assertGreaterEqual(result, 0)
 
     def test_max_value(self):
-        max_values = (0, 10, -1000, 1000, 999999)
+        max_values = (0, 10, -1000, 1000, 999999, 100000000000001.0, -1000000000000001.0)
 
         for max_value in max_values:
             result = self.fake.pyfloat(max_value=max_value)
@@ -210,6 +210,33 @@ class TestPyfloat(unittest.TestCase):
             self.assertLessEqual(result, 200.321)
             self.assertGreaterEqual(result, 100.123)
 
+    def test_max_none_and_min_value_with_decimals(self):
+        """
+        Combining the max_value and min_value keyword arguments with
+        positive value for min and None for max produces numbers with
+        left and right digits specified produced numbers greater than
+        the supplied min_value.
+        """
+        for _ in range(1000):
+            result = self.fake.pyfloat(min_value=99884.7, max_value=None, left_digits=5, right_digits=2)
+            self.assertGreaterEqual(result, 99884.7)
+            assert len(str(result).strip("-").split(".")[0]) == 5
+            assert len(str(result).strip("-").split(".")[1]) <= 2
+
+    def test_min_none_and_max_value_with_decimals(self):
+        """
+        Combining the max_value and min_value keyword arguments with
+        positive value for max and None for min produces numbers with
+        left and right digits specified produced numbers greater than
+        the supplied min_value.
+        """
+        for _ in range(1000):
+            result = self.fake.pyfloat(min_value=None, max_value=11000.3, left_digits=5, right_digits=2)
+            self.assertLessEqual(result, 11000.3)
+            assert len(str(result).strip("-").split(".")[0]) == 5
+            assert len(str(result).strip("-").split(".")[1]) <= 2
+
+
     def test_max_and_min_value_negative(self):
         """
         Combining the max_value and min_value keyword arguments with