Repository: jagerber48/sciform

Issue:
Rounding configuration
Recently `ndigits` was changed to have an `"all"` and `"pdg"` mode. An alternative was to have these two options be on `round_mode`, I think this latter would be more natural. I chose the former in anticipation of the resolution to #177 where I thought users might specify a `fallback_ndigits` to use for the value when the uncertainty is invalid. Doing it this way gives users a little more control on the fallback. But I realized users could have FULL control over the fallback if they just provide an additional formatter. This pollutes the option space even less in my opinion. Because of this I think the `all` and `pdg` options should be moved into `round_mode` with `ndigits` always being an integer and only used in `sig_fig` and `dec_place` modes.

Hints from issue comments:

Solution patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a67f765e..7d458325 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -17,11 +17,22 @@ Added
 Changed
 ^^^^^^^
 
+* **[BREAKING]** Rounding configuration has been refactored.
+  Previously ``round_mode`` accepted only ``"sig_fig"`` and ``"dec_place"``
+  string literals, all digits rounding mode was selected by settings
+  ``ndigits = AutoDigits`` and PDG rounding mode was selected by setting
+  ``pdg_sig_figs = True``.
+  Now ``round_mode`` accepts ``"sig_fig"``, ``"dec_place"``, ``"pdg"`` and
+  ``"all"`` string literals, ``ndigits`` only accepts integers, and the
+  ``pdg_sig_figs`` option is removed.
+  ``round_mode`` defaults to ``"all"`` and ``ndigits`` defaults to 2.
+  [`#185 <https://github.com/jagerber48/sciform/issues/185>`_]
 * **[BREAKING]** Previously ``exp_val`` and ``ndigits`` accepted the enums
   ``AutoExpVal`` and ``AutoDigits``.
-  Now ``exp_val`` accepts the string literal ``"auto"`` and ``ndigits`` accepts
-  the string literals ``"all"`` and ``"pdg"``.
-  [`#178 <https://github.com/jagerber48/sciform/issues/178>`_]
+  Now ``exp_val`` accepts the string literal ``"auto"`` and ``ndigits`` only
+  accepts integers.
+  [`#178 <https://github.com/jagerber48/sciform/issues/178>`_,
+  `#185 <https://github.com/jagerber48/sciform/issues/185>`_]
 * Previously during value/uncertainty formatting, if the uncertainty was invalid
   (0, ``nan`` or ``inf``) and the value was 0, the number would be formatted
   with as many digits as significant figures requested, e.g. ``0.00 ± nan``.
@@ -44,7 +55,7 @@ Changed
 REMOVED
 ^^^^^^^
 * The ``pdg_sig_figs`` options has been removed.
-  This option is now configured by setting ``ndigits="pdg"``.
+  This option is now configured by setting ``round_mode="pdg"``.
 
 FIXED
 ^^^^^
diff --git a/docs/source/examples.rst b/docs/source/examples.rst
index 3af32223..f314beec 100644
--- a/docs/source/examples.rst
+++ b/docs/source/examples.rst
@@ -68,7 +68,7 @@ available formatting options.
 >>> formatter = Formatter(
 ...     exp_mode="engineering",
 ...     exp_format="prefix",
-...     ndigits="pdg",
+...     round_mode="pdg",
 ...     pm_whitespace=False,
 ... )
 >>> print(formatter(num, unc))
@@ -77,7 +77,7 @@ available formatting options.
 >>> num = 314159.27
 >>> unc = 1618
 >>> formatter = Formatter(
-...     exp_mode="engineering_shifted", ndigits="pdg", paren_uncertainty=True
+...     exp_mode="engineering_shifted", round_mode="pdg", paren_uncertainty=True
 ... )
 >>> print(formatter(num, unc))
 0.3142(16)e+06
@@ -175,13 +175,13 @@ instead.
 >>> num_unc = SciNum(3141592.7, 1618)
 >>> print(f"{num_unc}")
 3141593 ± 1618
->>> with GlobalOptionsContext(ndigits="pdg", pm_whitespace=False):
+>>> with GlobalOptionsContext(round_mode="pdg", pm_whitespace=False):
 ...     print(f"{num_unc:rp}")
 ...
 (3.1416±0.0016) M
 
 >>> num_unc = SciNum(314159.27, 1618)
->>> with GlobalOptionsContext(ndigits="pdg"):
+>>> with GlobalOptionsContext(round_mode="pdg"):
 ...     print(f"{num_unc:#r()}")
 ...
 0.3142(16)e+06
@@ -298,13 +298,13 @@ lists, arrays, etc.) of numbers.
 >>> val_formatter = Formatter(
 ...     exp_mode="engineering",
 ...     exp_format="prefix",
-...     ndigits="all",
+...     round_mode="all",
 ...     paren_uncertainty=True,
 ... )
 >>> val_err_formatter = Formatter(
 ...     exp_mode="engineering",
 ...     exp_format="prefix",
-...     ndigits="pdg",
+...     round_mode="pdg",
 ...     paren_uncertainty=True,
 ... )
 >>> val_list = [1000, 2000, 3000]
diff --git a/docs/source/options.rst b/docs/source/options.rst
index 81c87bae..d2126f65 100644
--- a/docs/source/options.rst
+++ b/docs/source/options.rst
@@ -352,13 +352,15 @@ Rounding
 * Digits-past-the-deciaml rounding is selected by setting
   ``round_mode="dec_place"`` and passing an :class:`int` for ``ndigits``
   specifying the digit place to which to round.
-* All digits rounding is selected by setting ``ndigits="all"``.
+* All digits rounding is selected by setting ``round_mode="all"``.
   In this mode "all" digits of the number are shown where the definition of
-  "all" depends on the type of the numerical input data. See below for
-  more details. In this mode the ``round_mode`` option has no effect.
-* Particle data group (PDG) rounding is selected by setting ``ndigits="pdg"``.
-  See below for the defintion of PDG rounding. In this mode the ``round_mode``
-  option has no effect.
+  "all" depends on the type of the numerical input data.
+  See below for more details.
+  In this mode the ``ndigits`` option has no effect.
+* Particle data group (PDG) rounding is selected by setting
+  ``round_mode="pdg"``.
+  See below for the defintion of PDG rounding.
+  In this mode the ``ndigits`` option has no effect.
 
 Rounding always applies to the mantissa determined after identifying the
 appropriate exponent for display based on ``exp_mode`` and ``exp_val``.
@@ -461,8 +463,8 @@ Unlike the built in number formatting, it is possible for ``ndigits <= 0``:
 All Digits
 ----------
 
-"All" digits rounding is selected by setting ``ndigits="all"``.
-In this case, the ``round_mode`` option is ignored.
+"All" digits rounding is selected by setting ``round_mode="all"``.
+In this case, the ``ndigits`` option is ignored.
 This mode attempts to display the input digits with full precision.
 What exactly "full precision" means depends on the type of the input.
 :class:`int`, :class:`str` and :class:`Decimal` inputs are converted to
@@ -482,7 +484,7 @@ the result of ``"all"`` rounding will print the same digits as present in the
 string representation of the input :class:`float`.
 
 >>> from decimal import Decimal
->>> formatter = Formatter(exp_mode="scientific", ndigits="all")
+>>> formatter = Formatter(exp_mode="scientific", round_mode="all")
 >>> float_num = 1/9
 >>> dec_num = Decimal(float_num).normalize()
 >>> print(float_num)
@@ -497,8 +499,8 @@ string representation of the input :class:`float`.
 PDG Significant Figures
 -----------------------
 
-The PDG rounding is selected by setting ``ndigits="pdg"``.
-In this case, the ``round_mode`` option is ignored.
+The PDG rounding is selected by setting ``round_mode="pdg"``.
+In this case, the ``ndigits`` option is ignored.
 
 Typically value/uncertainty pairs are formatted with one or two
 significant figures displayed for the uncertainty.
@@ -537,8 +539,7 @@ For a more thorough discussion see
 `Significant digit 354 rule from Particle Data Group <https://stats.stackexchange.com/q/100502/260369>`_.
 
 >>> formatter = Formatter(
-...     round_mode="sig_fig",
-...     ndigits="pdg",
+...     round_mode="pdg",
 ... )
 >>> print(formatter(0.0123))
 0.012
diff --git a/docs/source/usage.rst b/docs/source/usage.rst
index 893f98f3..098b64ac 100644
--- a/docs/source/usage.rst
+++ b/docs/source/usage.rst
@@ -73,7 +73,7 @@ using the :class:`SciNum` object.
 
 >>> val = 84.3
 >>> unc = 0.2
->>> formatter = Formatter(ndigits=2)
+>>> formatter = Formatter(round_mode="sig_fig", ndigits=2)
 >>> print(formatter(val, unc))
 84.30 ± 0.20
 >>> from sciform import SciNum
@@ -118,7 +118,7 @@ types for the uncertainty.
 >>> from decimal import Decimal
 >>> from sciform import Formatter
 >>>
->>> formatter = Formatter(ndigits=4)
+>>> formatter = Formatter(round_mode="sig_fig", ndigits=4)
 >>> print(formatter("32", "9"))
 32.000 ± 9.000
 >>> print(formatter(32, 9))
@@ -396,7 +396,12 @@ These conversions can be accessed via the
 >>> print(f"{formatted} -> {formatted.as_ascii()}")
 (12.345_678 ± 0.000_255)% -> (12.345_678 +/- 0.000_255)%
 
->>> formatter = Formatter(exp_mode="engineering", exp_format="prefix", ndigits=4)
+>>> formatter = Formatter(
+...     exp_mode="engineering",
+...     exp_format="prefix",
+...     round_mode="sig_fig",
+...     ndigits=4,
+... )
 >>> formatted = formatter(314.159e-6, 2.71828e-6)
 >>> print(f"{formatted} -> {formatted.as_latex()}")
 (314.159 ± 2.718) μ -> $(314.159\:\pm\:2.718)\:\text{\textmu}$
@@ -408,7 +413,12 @@ These conversions can be accessed via the
 The LaTeX enclosing ``"$"`` math environment symbols can be optionally
 stripped:
 
->>> formatter = Formatter(exp_mode="engineering", exp_format="prefix", ndigits=4)
+>>> formatter = Formatter(
+...     exp_mode="engineering",
+...     exp_format="prefix",
+...     round_mode="sig_fig",
+...     ndigits=4,
+... )
 >>> formatted = formatter(314.159e-6, 2.71828e-6)
 >>> print(f"{formatted} -> {formatted.as_latex(strip_math_mode=False)}")
 (314.159 ± 2.718) μ -> $(314.159\:\pm\:2.718)\:\text{\textmu}$
@@ -454,8 +464,8 @@ The :mod:`sciform` default global options can be viewed using
 PopulatedOptions(
  'exp_mode': 'fixed_point',
  'exp_val': 'auto',
- 'round_mode': 'sig_fig',
- 'ndigits': 'all',
+ 'round_mode': 'all',
+ 'ndigits': 2,
  'upper_separator': '',
  'decimal_separator': '.',
  'lower_separator': '',
@@ -488,6 +498,7 @@ options using :func:`reset_global_options`.
 >>> set_global_options(
 ...     left_pad_char="0",
 ...     exp_mode="engineering_shifted",
+...     round_mode="sig_fig",
 ...     ndigits=4,
 ...     decimal_separator=",",
 ... )
diff --git a/src/sciform/api/formatter.py b/src/sciform/api/formatter.py
index 5d9e52e2..28152f0b 100644
--- a/src/sciform/api/formatter.py
+++ b/src/sciform/api/formatter.py
@@ -144,7 +144,7 @@ def __init__(  # noqa: PLR0913
         exp_mode: option_types.ExpMode | None = None,
         exp_val: int | option_types.ExpVal | None = None,
         round_mode: option_types.RoundMode | None = None,
-        ndigits: int | option_types.NDigits | None = None,
+        ndigits: int | None = None,
         upper_separator: option_types.UpperSeparators | None = None,
         decimal_separator: option_types.DecimalSeparators | None = None,
         lower_separator: option_types.LowerSeparators | None = None,
@@ -172,8 +172,7 @@ def __init__(  # noqa: PLR0913
         The following checks are performed when creating a new
         :class:`Formatter` object:
 
-        * If ``ndigits`` is an :class:`int` and ``round_mode`` is ``"sig_fig"``
-          then ``ndigits`` >= 1.
+        * If ``round_mode`` is ``"sig_fig"`` then ``ndigits`` >= 1.
         * ``exp_val`` must be consistent with the exponent mode. If
           ``exp_val`` is specified (i.e. not ``None``) and ``exp_val``
           is not ``"auto"`` then
@@ -200,14 +199,10 @@ def __init__(  # noqa: PLR0913
         :type exp_val: ``int | Literal['auto'] | None``
         :param round_mode: Indicate how to round numbers during
           formatting.
-        :type round_mode: ``Literal['sig_fig', 'dec_place'] | None``
-        :param ndigits: If ``ndigits`` is an :class:`int` then it specifies how
-          many digits to use for significant figure or digits-past-the-decimal
-          rounding. Can also be ``"all"`` to display as many digits needed to
-          reproduce the input representation of the number, or ``"pdg"`` to
-          automatically select the number of digits according to the PDG
-          rounding rules.
-        :type ndigits: ``int | Literal['all', 'pdg'] | None``
+        :type round_mode: ``Literal['sig_fig', 'dec_place', 'all', 'pdg'] | None``
+        :param ndigits: Specifies how many digits to use for significant figure
+          or digits-past-the-decimal rounding.
+        :type ndigits: ``int | None``
         :param upper_separator: Separator character to be used to group
           digits above the decimal symbol.
         :type upper_separator: ``Literal['', ',', '.', ' ', '_'] | None``
diff --git a/src/sciform/format_utils/rounding.py b/src/sciform/format_utils/rounding.py
index edf524a7..0b52041f 100644
--- a/src/sciform/format_utils/rounding.py
+++ b/src/sciform/format_utils/rounding.py
@@ -8,10 +8,7 @@
     get_bottom_dec_place,
     get_top_dec_place,
 )
-from sciform.options.option_types import (
-    NDigitsEnum,
-    RoundModeEnum,
-)
+from sciform.options.option_types import RoundModeEnum
 
 
 def get_pdg_round_digit(num: Decimal) -> int:
@@ -59,13 +56,13 @@ def get_pdg_round_digit(num: Decimal) -> int:
 def get_round_dec_place(
     num: Decimal,
     round_mode: RoundModeEnum,
-    ndigits: int | NDigitsEnum,
+    ndigits: int,
 ) -> int:
     """Get the decimal place to which to round."""
     # TODO: Handle nan and inf
-    if ndigits is NDigitsEnum.ALL:
+    if round_mode is RoundModeEnum.ALL:
         round_digit = get_bottom_dec_place(num)
-    elif ndigits is NDigitsEnum.PDG:
+    elif round_mode is RoundModeEnum.PDG:
         round_digit = get_pdg_round_digit(num)
     elif round_mode is RoundModeEnum.SIG_FIG:
         round_digit = get_top_dec_place(num) - (ndigits - 1)
@@ -80,13 +77,14 @@ def get_round_dec_place(
 def round_val_unc(
     val: Decimal,
     unc: Decimal,
-    ndigits: int | NDigitsEnum,
+    round_mode: RoundModeEnum,
+    ndigits: int,
 ) -> tuple[Decimal, Decimal, int]:
     """Simultaneously round the value and uncertainty."""
     if unc.is_finite() and unc != 0:
         round_digit = get_round_dec_place(
             unc,
-            RoundModeEnum.SIG_FIG,
+            round_mode,
             ndigits,
         )
         unc_rounded = round(unc, -round_digit)
@@ -97,7 +95,7 @@ def round_val_unc(
     elif val.is_finite() and val != 0:
         round_digit = get_round_dec_place(
             val,
-            RoundModeEnum.SIG_FIG,
+            round_mode,
             ndigits,
         )
         unc_rounded = unc
diff --git a/src/sciform/formatting/number_formatting.py b/src/sciform/formatting/number_formatting.py
index 2998e034..f8b86470 100644
--- a/src/sciform/formatting/number_formatting.py
+++ b/src/sciform/formatting/number_formatting.py
@@ -219,11 +219,13 @@ def format_val_unc(val: Decimal, unc: Decimal, options: FinalizedOptions) -> str
     val_rounded, unc_rounded, _ = round_val_unc(
         val,
         unc,
+        options.round_mode,
         options.ndigits,
     )
     val_rounded, unc_rounded, round_digit = round_val_unc(
         val_rounded,
         unc_rounded,
+        options.round_mode,
         options.ndigits,
     )
 
diff --git a/src/sciform/options/conversion.py b/src/sciform/options/conversion.py
index cf2fa704..b1b5ea46 100644
--- a/src/sciform/options/conversion.py
+++ b/src/sciform/options/conversion.py
@@ -92,7 +92,6 @@ def populate_options(input_options: InputOptions) -> PopulatedOptions:
     "exp_mode": option_types.ExpModeEnum,
     "exp_val": option_types.ExpValEnum,
     "round_mode": option_types.RoundModeEnum,
-    "ndigits": option_types.NDigitsEnum,
     "upper_separator": option_types.SeparatorEnum,
     "decimal_separator": option_types.SeparatorEnum,
     "lower_separator": option_types.SeparatorEnum,
diff --git a/src/sciform/options/finalized_options.py b/src/sciform/options/finalized_options.py
index 3cf2af06..c109a025 100644
--- a/src/sciform/options/finalized_options.py
+++ b/src/sciform/options/finalized_options.py
@@ -21,7 +21,7 @@ class FinalizedOptions:
     exp_mode: option_types.ExpModeEnum
     exp_val: int | option_types.ExpValEnum
     round_mode: option_types.RoundModeEnum
-    ndigits: int | option_types.NDigitsEnum
+    ndigits: int
     upper_separator: option_types.UpperSeparatorEnums
     decimal_separator: option_types.DecimalSeparatorEnums
     lower_separator: option_types.LowerSeparatorEnums
diff --git a/src/sciform/options/global_options.py b/src/sciform/options/global_options.py
index 92d938ee..ee923e03 100644
--- a/src/sciform/options/global_options.py
+++ b/src/sciform/options/global_options.py
@@ -5,8 +5,8 @@
 PKG_DEFAULT_OPTIONS = PopulatedOptions(
     exp_mode="fixed_point",
     exp_val="auto",
-    round_mode="sig_fig",
-    ndigits="all",
+    round_mode="all",
+    ndigits=2,
     upper_separator="",
     decimal_separator=".",
     lower_separator="",
diff --git a/src/sciform/options/input_options.py b/src/sciform/options/input_options.py
index c8c7c8d7..c63c763f 100644
--- a/src/sciform/options/input_options.py
+++ b/src/sciform/options/input_options.py
@@ -53,7 +53,7 @@ class InputOptions:
     exp_mode: option_types.ExpMode | None = None
     exp_val: int | option_types.ExpVal | None = None
     round_mode: option_types.RoundMode | None = None
-    ndigits: int | option_types.NDigits | None = None
+    ndigits: int | None = None
     upper_separator: option_types.UpperSeparators | None = None
     decimal_separator: option_types.DecimalSeparators | None = None
     lower_separator: option_types.LowerSeparators | None = None
diff --git a/src/sciform/options/option_types.py b/src/sciform/options/option_types.py
index fa6ccc8a..c0463633 100644
--- a/src/sciform/options/option_types.py
+++ b/src/sciform/options/option_types.py
@@ -56,7 +56,7 @@ class SeparatorEnum(str, Enum):
 ]
 
 
-RoundMode = Literal["sig_fig", "dec_place"]
+RoundMode = Literal["sig_fig", "dec_place", "all", "pdg"]
 
 
 class RoundModeEnum(str, Enum):
@@ -64,14 +64,6 @@ class RoundModeEnum(str, Enum):
 
     SIG_FIG = "sig_fig"
     DEC_PLACE = "dec_place"
-
-
-NDigits = Literal["all", "pdg"]
-
-
-class NDigitsEnum(str, Enum):
-    """Number of digits Enum."""
-
     ALL = "all"
     PDG = "pdg"
 
diff --git a/src/sciform/options/populated_options.py b/src/sciform/options/populated_options.py
index 6c0c44d1..b067461a 100644
--- a/src/sciform/options/populated_options.py
+++ b/src/sciform/options/populated_options.py
@@ -94,7 +94,7 @@ class PopulatedOptions:
     exp_mode: option_types.ExpMode
     exp_val: int | option_types.ExpVal
     round_mode: option_types.RoundMode
-    ndigits: int | option_types.NDigits
+    ndigits: int
     upper_separator: option_types.UpperSeparators
     decimal_separator: option_types.DecimalSeparators
     lower_separator: option_types.LowerSeparators
diff --git a/src/sciform/options/validation.py b/src/sciform/options/validation.py
index 039e8ad5..d96bf098 100644
--- a/src/sciform/options/validation.py
+++ b/src/sciform/options/validation.py
@@ -28,7 +28,6 @@ def validate_options(
 
 
 allowed_round_modes = get_args(option_types.RoundMode)
-allowed_ndigits = get_args(option_types.NDigits)
 
 
 def validate_rounding(
@@ -39,10 +38,8 @@ def validate_rounding(
     r"""Validate ndigits if round_mode == "sig_fig"."""
     if none_allowed and options.ndigits is None:
         pass
-    elif (
-        not isinstance(options.ndigits, int) and options.ndigits not in allowed_ndigits
-    ):
-        msg = f"ndigits must be an int or in {allowed_ndigits}, not {options.ndigits}."
+    elif not isinstance(options.ndigits, int):
+        msg = f"ndigits must be an int, not {options.ndigits}."
         raise TypeError(msg)
 
     if none_allowed and options.round_mode is None:
@@ -54,11 +51,7 @@ def validate_rounding(
                 f"{options.round_mode}."
             )
             raise ValueError(msg)
-        if (
-            options.round_mode == "sig_fig"
-            and isinstance(options.ndigits, int)
-            and options.ndigits < 1
-        ):
+        if options.round_mode == "sig_fig" and options.ndigits < 1:
             msg = f"ndigits must be >= 1 for sig fig rounding, not {options.ndigits}."
             raise ValueError(msg)
 


Test patch:
diff --git a/tests/feature/test_options_printout.py b/tests/feature/test_options_printout.py
index 99b90980..3b8ea8ee 100644
--- a/tests/feature/test_options_printout.py
+++ b/tests/feature/test_options_printout.py
@@ -39,7 +39,7 @@ def test_formatter_populated_options(self):
             "PopulatedOptions(\n"
             " 'exp_mode': 'engineering',\n"
             " 'exp_val': 'auto',\n"
-            " 'round_mode': 'sig_fig',\n"
+            " 'round_mode': 'all',\n"
             " 'ndigits': 10,\n"
             " 'upper_separator': '',\n"
             " 'decimal_separator': '.',\n"
@@ -68,8 +68,8 @@ def test_get_default_global_options(self):
             "PopulatedOptions(\n"
             " 'exp_mode': 'fixed_point',\n"
             " 'exp_val': 'auto',\n"
-            " 'round_mode': 'sig_fig',\n"
-            " 'ndigits': 'all',\n"
+            " 'round_mode': 'all',\n"
+            " 'ndigits': 2,\n"
             " 'upper_separator': '',\n"
             " 'decimal_separator': '.',\n"
             " 'lower_separator': '',\n"
@@ -98,8 +98,8 @@ def test_get_global_options(self):
             "PopulatedOptions(\n"
             " 'exp_mode': 'fixed_point',\n"
             " 'exp_val': 'auto',\n"
-            " 'round_mode': 'sig_fig',\n"
-            " 'ndigits': 'all',\n"
+            " 'round_mode': 'all',\n"
+            " 'ndigits': 2,\n"
             " 'upper_separator': '',\n"
             " 'decimal_separator': '.',\n"
             " 'lower_separator': '',\n"
diff --git a/tests/feature/test_output_conversion.py b/tests/feature/test_output_conversion.py
index c4e37377..f67e7440 100644
--- a/tests/feature/test_output_conversion.py
+++ b/tests/feature/test_output_conversion.py
@@ -431,6 +431,7 @@ def test_val_unc_formatter_cases(self):
                         Formatter(
                             exp_mode="engineering",
                             exp_format="prefix",
+                            round_mode="sig_fig",
                             ndigits=4,
                         ),
                         (
diff --git a/tests/feature/test_val_formatter.py b/tests/feature/test_val_formatter.py
index 9e466317..7216880c 100644
--- a/tests/feature/test_val_formatter.py
+++ b/tests/feature/test_val_formatter.py
@@ -290,7 +290,7 @@ def test_no_options(self):
         self.assertEqual(formatter(42), "42")
 
     def test_dec_place_auto_round(self):
-        formatter = Formatter(round_mode="dec_place", ndigits="all")
+        formatter = Formatter(round_mode="all")
         self.assertEqual(formatter(123.456), "123.456")
 
     def test_pdg_sig_figs(self):
@@ -299,15 +299,15 @@ def test_pdg_sig_figs(self):
                 6789,
                 [
                     (
-                        Formatter(ndigits="pdg"),
+                        Formatter(round_mode="pdg"),
                         "7000",
                     ),
                     (
-                        Formatter(ndigits=5),
+                        Formatter(round_mode="sig_fig", ndigits=5),
                         "6789.0",
                     ),
                     (
-                        Formatter(ndigits="all"),
+                        Formatter(round_mode="all"),
                         "6789",
                     ),
                 ],
@@ -317,7 +317,7 @@ def test_pdg_sig_figs(self):
         self.run_val_formatter_cases(cases_list)
 
     def test_decimal_normalization(self):
-        formatter = Formatter(ndigits="all")
+        formatter = Formatter(round_mode="all")
         self.assertEqual(formatter(Decimal("1.0")), formatter(Decimal("1.00")))
 
     def test_long_decimal(self):
@@ -329,7 +329,7 @@ def test_long_decimal(self):
                         Formatter(
                             exp_mode="engineering",
                             exp_format="prefix",
-                            ndigits="all",
+                            round_mode="all",
                             upper_separator=" ",
                             lower_separator=" ",
                         ),
@@ -343,7 +343,7 @@ def test_long_decimal(self):
                     (
                         Formatter(
                             exp_mode="fixed_point",
-                            ndigits="all",
+                            round_mode="all",
                         ),
                         "123456789987654321.123456789987654321",
                     ),
diff --git a/tests/feature/test_val_unc_formatter.py b/tests/feature/test_val_unc_formatter.py
index 6baa90e4..dde7e571 100644
--- a/tests/feature/test_val_unc_formatter.py
+++ b/tests/feature/test_val_unc_formatter.py
@@ -262,73 +262,73 @@ def test_pdg(self):
             (
                 (10, 0.0353),
                 [
-                    (Formatter(ndigits="pdg"), "10.000 ± 0.035"),
+                    (Formatter(round_mode="pdg"), "10.000 ± 0.035"),
                 ],
             ),
             (
                 (10, 0.0354),
                 [
-                    (Formatter(ndigits="pdg"), "10.000 ± 0.035"),
+                    (Formatter(round_mode="pdg"), "10.000 ± 0.035"),
                 ],
             ),
             (
                 (10, 0.03544),
                 [
-                    (Formatter(ndigits="pdg"), "10.000 ± 0.035"),
+                    (Formatter(round_mode="pdg"), "10.000 ± 0.035"),
                 ],
             ),
             (
                 (10, 0.03546),
                 [
-                    (Formatter(ndigits="pdg"), "10.000 ± 0.035"),
+                    (Formatter(round_mode="pdg"), "10.000 ± 0.035"),
                 ],
             ),
             (
                 (10, 0.0355),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.04"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.04"),
                 ],
             ),
             (
                 (10, 0.0949),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.09"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.09"),
                 ],
             ),
             (
                 (10, 0.09494),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.09"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.09"),
                 ],
             ),
             (
                 (10, 0.09496),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.09"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.09"),
                 ],
             ),
             (
                 (10, 0.0950),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.10"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.10"),
                 ],
             ),
             (
                 (10, 0.0951),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.10"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.10"),
                 ],
             ),
             (
                 (10, 0.09994),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.10"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.10"),
                 ],
             ),
             (
                 (10, 0.09996),
                 [
-                    (Formatter(ndigits="pdg"), "10.00 ± 0.10"),
+                    (Formatter(round_mode="pdg"), "10.00 ± 0.10"),
                 ],
             ),
             (
@@ -338,7 +338,7 @@ def test_pdg(self):
                         Formatter(
                             exp_mode="engineering",
                             exp_format="prefix",
-                            ndigits="pdg",
+                            round_mode="pdg",
                         ),
                         "(3.1416 ± 0.0016) M",
                     ),
@@ -353,37 +353,37 @@ def test_pdg_invalid_unc(self):
             (
                 (123, 0),
                 [
-                    (Formatter(ndigits="pdg"), "120 ± 0"),
+                    (Formatter(round_mode="pdg"), "120 ± 0"),
                 ],
             ),
             (
                 (-123, 0),
                 [
-                    (Formatter(ndigits="pdg"), "-120 ± 0"),
+                    (Formatter(round_mode="pdg"), "-120 ± 0"),
                 ],
             ),
             (
                 (0, 0),
                 [
-                    (Formatter(ndigits="pdg"), "0 ± 0"),
+                    (Formatter(round_mode="pdg"), "0 ± 0"),
                 ],
             ),
             (
                 (123, float("nan")),
                 [
-                    (Formatter(ndigits="pdg"), "120 ± nan"),
+                    (Formatter(round_mode="pdg"), "120 ± nan"),
                 ],
             ),
             (
                 (-123, float("nan")),
                 [
-                    (Formatter(ndigits="pdg"), "-120 ± nan"),
+                    (Formatter(round_mode="pdg"), "-120 ± nan"),
                 ],
             ),
             (
                 (0, float("nan")),
                 [
-                    (Formatter(ndigits="pdg"), "0 ± nan"),
+                    (Formatter(round_mode="pdg"), "0 ± nan"),
                 ],
             ),
         ]
@@ -409,15 +409,15 @@ def test_pdg_sig_figs(self):
                 (7, 0.1234),
                 [
                     (
-                        Formatter(ndigits="pdg"),
+                        Formatter(round_mode="pdg"),
                         "7.00 ± 0.12",
                     ),
                     (
-                        Formatter(ndigits=5),
+                        Formatter(round_mode="sig_fig", ndigits=5),
                         "7.00000 ± 0.12340",
                     ),
                     (
-                        Formatter(ndigits="all"),
+                        Formatter(round_mode="all"),
                         "7.0000 ± 0.1234",
                     ),
                 ],
@@ -614,7 +614,7 @@ def test_paren_uncertainties_trim_digits_bipm(self):
         self.run_val_unc_formatter_cases(cases_list)
 
     def test_decimal_normalization(self):
-        formatter = Formatter(ndigits="all")
+        formatter = Formatter(round_mode="all")
         self.assertEqual(
             formatter(Decimal("100.0"), Decimal("1.00")),
             formatter(Decimal("100"), Decimal("1.0000")),
diff --git a/tests/unit/format_utils/test_rounding_utils.py b/tests/unit/format_utils/test_rounding_utils.py
index f84163c1..57b2f639 100644
--- a/tests/unit/format_utils/test_rounding_utils.py
+++ b/tests/unit/format_utils/test_rounding_utils.py
@@ -1,20 +1,20 @@
 from __future__ import annotations
 
 from decimal import Decimal
-from typing import Tuple, Union
+from typing import Tuple
 
 from sciform.format_utils import rounding
-from sciform.options.option_types import NDigitsEnum, RoundModeEnum
+from sciform.options.option_types import RoundModeEnum
 
 from tests import NanTestCase
 
 RoundDecPlaceCase = Tuple[
-    Tuple[Decimal, RoundModeEnum, Union[int, NDigitsEnum]],
+    Tuple[Decimal, RoundModeEnum, int],
     int,
 ]
 
 RoundValUncCase = Tuple[
-    Tuple[Decimal, Decimal, Union[int, NDigitsEnum]],
+    Tuple[Decimal, Decimal, RoundModeEnum, int],
     Tuple[Decimal, Decimal, int],
 ]
 
@@ -114,46 +114,26 @@ def test_get_round_dec_place(self):
             ((Decimal("0.0123456"), RoundModeEnum.DEC_PLACE, -2), 2),
             ((Decimal("0.00123456"), RoundModeEnum.DEC_PLACE, -2), 2),
             ((Decimal("0.000123456"), RoundModeEnum.DEC_PLACE, -2), 2),
-            ((Decimal("123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), 0),
-            ((Decimal("12345.6"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -1),
-            ((Decimal("1234.56"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -2),
-            ((Decimal("123.456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -3),
-            ((Decimal("12.3456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -4),
-            ((Decimal("1.23456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -5),
-            ((Decimal("0.123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -6),
-            ((Decimal("0.0123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -7),
-            ((Decimal("0.00123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -8),
-            ((Decimal("0.000123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.ALL), -9),
-            ((Decimal("123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), 0),
-            ((Decimal("12345.6"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -1),
-            ((Decimal("1234.56"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -2),
-            ((Decimal("123.456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -3),
-            ((Decimal("12.3456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -4),
-            ((Decimal("1.23456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -5),
-            ((Decimal("0.123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -6),
-            ((Decimal("0.0123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -7),
-            ((Decimal("0.00123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -8),
-            ((Decimal("0.000123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.ALL), -9),
-            ((Decimal("123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), 4),
-            ((Decimal("12345.6"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), 3),
-            ((Decimal("1234.56"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), 2),
-            ((Decimal("123.456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), 1),
-            ((Decimal("12.3456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), 0),
-            ((Decimal("1.23456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), -1),
-            ((Decimal("0.123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), -2),
-            ((Decimal("0.0123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), -3),
-            ((Decimal("0.00123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), -4),
-            ((Decimal("0.000123456"), RoundModeEnum.SIG_FIG, NDigitsEnum.PDG), -5),
-            ((Decimal("123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), 4),
-            ((Decimal("12345.6"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), 3),
-            ((Decimal("1234.56"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), 2),
-            ((Decimal("123.456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), 1),
-            ((Decimal("12.3456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), 0),
-            ((Decimal("1.23456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), -1),
-            ((Decimal("0.123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), -2),
-            ((Decimal("0.0123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), -3),
-            ((Decimal("0.00123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), -4),
-            ((Decimal("0.000123456"), RoundModeEnum.DEC_PLACE, NDigitsEnum.PDG), -5),
+            ((Decimal("123456"), RoundModeEnum.ALL, 0), 0),
+            ((Decimal("12345.6"), RoundModeEnum.ALL, 0), -1),
+            ((Decimal("1234.56"), RoundModeEnum.ALL, 0), -2),
+            ((Decimal("123.456"), RoundModeEnum.ALL, 0), -3),
+            ((Decimal("12.3456"), RoundModeEnum.ALL, 0), -4),
+            ((Decimal("1.23456"), RoundModeEnum.ALL, 0), -5),
+            ((Decimal("0.123456"), RoundModeEnum.ALL, 0), -6),
+            ((Decimal("0.0123456"), RoundModeEnum.ALL, 0), -7),
+            ((Decimal("0.00123456"), RoundModeEnum.ALL, 0), -8),
+            ((Decimal("0.000123456"), RoundModeEnum.ALL, 0), -9),
+            ((Decimal("123456"), RoundModeEnum.PDG, 0), 4),
+            ((Decimal("12345.6"), RoundModeEnum.PDG, 0), 3),
+            ((Decimal("1234.56"), RoundModeEnum.PDG, 0), 2),
+            ((Decimal("123.456"), RoundModeEnum.PDG, 0), 1),
+            ((Decimal("12.3456"), RoundModeEnum.PDG, 0), 0),
+            ((Decimal("1.23456"), RoundModeEnum.PDG, 0), -1),
+            ((Decimal("0.123456"), RoundModeEnum.PDG, 0), -2),
+            ((Decimal("0.0123456"), RoundModeEnum.PDG, 0), -3),
+            ((Decimal("0.00123456"), RoundModeEnum.PDG, 0), -4),
+            ((Decimal("0.000123456"), RoundModeEnum.PDG, 0), -5),
         ]
 
         for input_data, expected_output in cases:
@@ -184,61 +164,61 @@ def test_get_round_dec_place_invalid(self):
     def test_round_val_unc(self):
         cases: list[RoundValUncCase] = [
             (
-                (Decimal("123"), Decimal("0.456"), 1),
+                (Decimal("123"), Decimal("0.456"), RoundModeEnum.SIG_FIG, 1),
                 (Decimal("123.0"), Decimal("0.5"), -1),
             ),
             (
-                (Decimal("123"), Decimal("0.456"), 4),
+                (Decimal("123"), Decimal("0.456"), RoundModeEnum.SIG_FIG, 4),
                 (Decimal("123.0000"), Decimal("0.4560"), -4),
             ),
             (
-                (Decimal("123"), Decimal("0.456"), NDigitsEnum.PDG),
+                (Decimal("123"), Decimal("0.456"), RoundModeEnum.PDG, 2),
                 (Decimal("123.0"), Decimal("0.5"), -1),
             ),
             (
-                (Decimal("123"), Decimal("0.456"), NDigitsEnum.PDG),
+                (Decimal("123"), Decimal("0.456"), RoundModeEnum.PDG, 2),
                 (Decimal("123.0"), Decimal("0.5"), -1),
             ),
             (
-                (Decimal("0.456"), Decimal("123"), 1),
+                (Decimal("0.456"), Decimal("123"), RoundModeEnum.SIG_FIG, 1),
                 (Decimal("0"), Decimal("100"), 2),
             ),
             (
-                (Decimal("0.456"), Decimal("123"), 4),
+                (Decimal("0.456"), Decimal("123"), RoundModeEnum.SIG_FIG, 4),
                 (Decimal("0.5"), Decimal("123.0"), -1),
             ),
             (
-                (Decimal("0.456"), Decimal("123"), NDigitsEnum.PDG),
+                (Decimal("0.456"), Decimal("123"), RoundModeEnum.PDG, 2),
                 (Decimal("0"), Decimal("120"), 1),
             ),
             (
-                (Decimal("123"), Decimal("nan"), 4),
+                (Decimal("123"), Decimal("nan"), RoundModeEnum.SIG_FIG, 4),
                 (Decimal("123.0"), Decimal("nan"), -1),
             ),
             (
-                (Decimal("nan"), Decimal("123"), 4),
+                (Decimal("nan"), Decimal("123"), RoundModeEnum.SIG_FIG, 4),
                 (Decimal("nan"), Decimal("123.0"), -1),
             ),
             (
-                (Decimal("nan"), Decimal("inf"), 4),
+                (Decimal("nan"), Decimal("inf"), RoundModeEnum.SIG_FIG, 4),
                 (Decimal("nan"), Decimal("inf"), 0),
             ),
             (
-                (Decimal("123"), Decimal("nan"), NDigitsEnum.PDG),
+                (Decimal("123"), Decimal("nan"), RoundModeEnum.PDG, 2),
                 (Decimal("120.0"), Decimal("nan"), 1),
             ),
             (
-                (Decimal("nan"), Decimal("123"), NDigitsEnum.PDG),
+                (Decimal("nan"), Decimal("123"), RoundModeEnum.PDG, 2),
                 (Decimal("nan"), Decimal("120"), 1),
             ),
             (
-                (Decimal("nan"), Decimal("inf"), NDigitsEnum.PDG),
+                (Decimal("nan"), Decimal("inf"), RoundModeEnum.PDG, 2),
                 (Decimal("nan"), Decimal("inf"), 0),
             ),
         ]
 
         for input_data, output_data in cases:
-            val, unc, ndigits = input_data
+            val, unc, round_mode, ndigits = input_data
             (
                 expected_val_rounded,
                 expected_unc_rounded,
@@ -251,11 +231,13 @@ def test_round_val_unc(self):
             ) = rounding.round_val_unc(
                 val,
                 unc,
+                round_mode,
                 ndigits,
             )
             with self.subTest(
                 val=val,
                 unc=unc,
+                round_mode=round_mode,
                 ndigits=ndigits,
             ):
                 self.assertNanEqual(expected_val_rounded, actual_val_rounded)