diff --git a/Tests/images/hopper_roundUp_2.tif b/Tests/images/hopper_float_dpi_2.tif
similarity index 100%
rename from Tests/images/hopper_roundUp_2.tif
rename to Tests/images/hopper_float_dpi_2.tif
diff --git a/Tests/images/hopper_roundUp_3.tif b/Tests/images/hopper_float_dpi_3.tif
similarity index 100%
rename from Tests/images/hopper_roundUp_3.tif
rename to Tests/images/hopper_float_dpi_3.tif
diff --git a/Tests/images/hopper_roundUp_None.tif b/Tests/images/hopper_float_dpi_None.tif
similarity index 100%
rename from Tests/images/hopper_roundUp_None.tif
rename to Tests/images/hopper_float_dpi_None.tif
diff --git a/Tests/images/hopper_roundDown_2.tif b/Tests/images/hopper_roundDown_2.tif
deleted file mode 100644
index ac8cd057d..000000000
Binary files a/Tests/images/hopper_roundDown_2.tif and /dev/null differ
diff --git a/Tests/images/hopper_roundDown_3.tif b/Tests/images/hopper_roundDown_3.tif
deleted file mode 100644
index 0542fab9a..000000000
Binary files a/Tests/images/hopper_roundDown_3.tif and /dev/null differ
diff --git a/Tests/images/hopper_roundDown_None.tif b/Tests/images/hopper_roundDown_None.tif
deleted file mode 100644
index 21c40e8fe..000000000
Binary files a/Tests/images/hopper_roundDown_None.tif and /dev/null differ
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index c24438c48..91506f980 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -137,29 +137,26 @@ class TestFileTiff:
             im._setup()
             assert im.info["dpi"] == (71.0, 71.0)
 
-    def test_load_dpi_rounding(self):
-        for resolutionUnit, dpi in ((None, (72, 73)), (2, (72, 73)), (3, (183, 185))):
-            with Image.open(
-                "Tests/images/hopper_roundDown_" + str(resolutionUnit) + ".tif"
-            ) as im:
-                assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
-                assert im.info["dpi"] == (dpi[0], dpi[0])
+    @pytest.mark.parametrize(
+        "resolutionUnit, dpi",
+        [(None, 72.8), (2, 72.8), (3, 184.912)],
+    )
+    def test_load_float_dpi(self, resolutionUnit, dpi):
+        with Image.open(
+            "Tests/images/hopper_float_dpi_" + str(resolutionUnit) + ".tif"
+        ) as im:
+            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
+            for reloaded_dpi in im.info["dpi"]:
+                assert float(reloaded_dpi) == dpi
 
-            with Image.open(
-                "Tests/images/hopper_roundUp_" + str(resolutionUnit) + ".tif"
-            ) as im:
-                assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
-                assert im.info["dpi"] == (dpi[1], dpi[1])
-
-    def test_save_dpi_rounding(self, tmp_path):
+    def test_save_float_dpi(self, tmp_path):
         outfile = str(tmp_path / "temp.tif")
         with Image.open("Tests/images/hopper.tif") as im:
-            for dpi in (72.2, 72.8):
-                im.save(outfile, dpi=(dpi, dpi))
+            im.save(outfile, dpi=(72.2, 72.2))
 
-                with Image.open(outfile) as reloaded:
-                    reloaded.load()
-                    assert (round(dpi), round(dpi)) == reloaded.info["dpi"]
+            with Image.open(outfile) as reloaded:
+                for dpi in reloaded.info["dpi"]:
+                    assert float(dpi) == 72.2
 
     def test_save_setting_missing_resolution(self):
         b = BytesIO()
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 8c0f61f47..0e779248f 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -358,6 +358,16 @@ class IFDRational(Rational):
             other = other._val
         return self._val == other
 
+    def __getstate__(self):
+        return [self._val, self._numerator, self._denominator]
+
+    def __setstate__(self, state):
+        IFDRational.__init__(self, 0)
+        _val, _numerator, _denominator = state
+        self._val = _val
+        self._numerator = _numerator
+        self._denominator = _denominator
+
     def _delegate(op):
         def delegate(self, *args):
             return getattr(self._val, op)(*args)
@@ -1284,11 +1294,11 @@ class TiffImageFile(ImageFile.ImageFile):
         if xres and yres:
             resunit = self.tag_v2.get(RESOLUTION_UNIT)
             if resunit == 2:  # dots per inch
-                self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
+                self.info["dpi"] = (xres, yres)
             elif resunit == 3:  # dots per centimeter. convert to dpi
-                self.info["dpi"] = int(xres * 2.54 + 0.5), int(yres * 2.54 + 0.5)
+                self.info["dpi"] = (xres * 2.54, yres * 2.54)
             elif resunit is None:  # used to default to 1, but now 2)
-                self.info["dpi"] = int(xres + 0.5), int(yres + 0.5)
+                self.info["dpi"] = (xres, yres)
                 # For backward compatibility,
                 # we also preserve the old behavior
                 self.info["resolution"] = xres, yres
@@ -1521,8 +1531,8 @@ def _save(im, fp, filename):
     dpi = im.encoderinfo.get("dpi")
     if dpi:
         ifd[RESOLUTION_UNIT] = 2
-        ifd[X_RESOLUTION] = int(dpi[0] + 0.5)
-        ifd[Y_RESOLUTION] = int(dpi[1] + 0.5)
+        ifd[X_RESOLUTION] = dpi[0]
+        ifd[Y_RESOLUTION] = dpi[1]
 
     if bits != (1,):
         ifd[BITSPERSAMPLE] = bits