docs: def round_value(val, unc=None, unc_down=None, method="publication"):
      """
      Rounds a number *val* with a single symmetric uncertainty *unc* or asymmetric uncertainties
      *unc* (interpreted as *up*) and *unc_down*, and calculates the orders of their magnitudes. They
      both can be a float or a list of floats for simultaneous evaluation. When *val* is a
      :py:class:`Number` instance, its combined uncertainty is used instead. Returns a 3-tuple
      containing:
  
      - The string representation of the central value.
      - The string representations of the uncertainties in a list. For the symmetric case, this list
        contains only one element.
      - The decimal magnitude.
  
      Examples:
  
      .. code-block:: python
  
          round_value(1.23, 0.456)        # -> ("123", ["46"], -2)
          round_value(1.23, 0.456, 0.987) # -> ("123", ["46", "99"], -2)
  
          round_value(1.23, [0.456, 0.312]) # -> ("123", [["456", "312"]], -3)
  
          vals = np.array([1.23, 4.56])
          uncs = np.array([0.45678, 0.078])
          round_value(vals, uncs) # -> (["1230", "4560"], [["457", "78"]], -3)
      """
      if isinstance(val, Number):
          unc, unc_down = val.get_uncertainty()
          val = val.nominal
      elif unc is None:
          raise ValueError("unc must be set when val is not a Number instance")
  
      # prepare unc values
      asym = unc_down is not None
      unc_up = unc
      if not asym:
          unc_down = unc_up
  
      if not is_numpy(val):
          # treat as lists for simultaneous rounding when not numpy arrays
          passed_list = isinstance(unc_up, (list, tuple)) or isinstance(unc_down, (list, tuple))
          unc_up = make_list(unc_up)
          unc_down = make_list(unc_down)
  
          # sanity checks
          if len(unc_up) != len(unc_down):
              raise ValueError("uncertainties should have same length when passed as lists")
          elif any(unc < 0 for unc in unc_up):
              raise ValueError("up uncertainties must be positive: {}".format(unc_up))
          elif any(unc < 0 for unc in unc_down):
              raise ValueError("down uncertainties must be positive: {}".format(unc_down))
  
          # to determine the precision, use the uncertainty with the smallest magnitude
          ref_mag = min(round_uncertainty(u, method=method)[1] for u in unc_up + unc_down)
  
          # convert the uncertainty and central value to match the reference magnitude
          scale = 1. / 10.**ref_mag
          val_str = match_precision(scale * val, "1")
          up_strs = [match_precision(scale * u, "1") for u in unc_up]
          down_strs = [match_precision(scale * u, "1") for u in unc_down]
  
          if passed_list:
              return (val_str, [up_strs, down_strs] if asym else [up_strs], ref_mag)
          else:
              return (val_str, [up_strs[0], down_strs[0]] if asym else [up_strs[0]], ref_mag)
  
      else:
          # sanity checks
          if (unc_up < 0).any():
              raise ValueError("up uncertainties must be positive: {}".format(unc_up))
          elif (unc_down < 0).any():
              raise ValueError("down uncertainties must be positive: {}".format(unc_down))
  
          # to determine the precision, use the uncertainty with the smallest magnitude
          ref_mag_up = round_uncertainty(unc_up, method=method)[1]
          ref_mag_down = round_uncertainty(unc_down, method=method)[1]
          ref_mag = min(ref_mag_up.min(), ref_mag_down.min())
  
          scale = 1. / 10.**ref_mag
          val_str = match_precision(scale * val, "1")
          up_str = match_precision(scale * unc_up, "1")
          down_str = match_precision(scale * unc_down, "1")
  
          return (val_str, [up_str, down_str] if asym else [up_str], ref_mag)