code: def evalf_mul(v: "Mul", prec: int, options: OPT_DICT) -> TMP_RES:
      res = pure_complex(v)
      if res:
          # the only pure complex that is a mul is h*I
          _, h = res
          im, _, im_acc, _ = evalf(h, prec, options)
          return None, im, None, im_acc
      args = list(v.args)
  
      # see if any argument is NaN or oo and thus warrants a special return
      has_zero = False
      special = []
      from .numbers import Float
  
      for arg in args:
          result = evalf(arg, prec, options)
          if result is S.ComplexInfinity:
              special.append(result)
              continue
          if result[0] is None:
              if result[1] is None:
                  has_zero = True
              continue
          num = Float._new(result[0], 1)
          if num is S.NaN:
              return fnan, None, prec, None
          if num.is_infinite:
              special.append(num)
      if special:
          if has_zero:
              return fnan, None, prec, None
          from .mul import Mul
  
          return evalf(Mul(*special), prec + 4, {})
      if has_zero:
          return None, None, None, None
  
      # With guard digits, multiplication in the real case does not destroy
      # accuracy. This is also true in the complex case when considering the
      # total accuracy; however accuracy for the real or imaginary parts
      # separately may be lower.
      acc = prec
  
      # XXX: big overestimate
      working_prec = prec + len(args) + 5
  
      # Empty product is 1
      start = man, exp, bc = MPZ(1), 0, 1
  
      # First, we multiply all pure real or pure imaginary numbers.
      # direction tells us that the result should be multiplied by
      # I**direction; all other numbers get put into complex_factors
      # to be multiplied out after the first phase.
      last = len(args)
      direction = 0
      args.append(S.One)
      complex_factors = []
  
      for i, arg in enumerate(args):
          if i != last and pure_complex(arg):
              args[-1] = (args[-1] * arg).expand()
              continue
          elif i == last and arg is S.One:
              continue
          re, im, re_acc, im_acc = evalf(arg, working_prec, options)
          if re and im:
              complex_factors.append((re, im, re_acc, im_acc))
              continue
          elif re:
              (s, m, e, b), w_acc = re, re_acc
          elif im:
              (s, m, e, b), w_acc = im, im_acc
              direction += 1
          else:
              return None, None, None, None
          direction += 2 * s
          man *= m
          exp += e
          bc += b
          while bc > 3 * working_prec:
              man >>= working_prec
              exp += working_prec
              bc -= working_prec
          acc = min(acc, w_acc)
      sign = (direction & 2) >> 1
      if not complex_factors:
          v = normalize(sign, man, exp, bitcount(man), prec, rnd)
          # multiply by i
          if direction & 1:
              return None, v, None, acc
          else:
              return v, None, acc, None
      else:
          # initialize with the first term
          if (man, exp, bc) != start:
              # there was a real part; give it an imaginary part
              re, im = (sign, man, exp, bitcount(man)), (
                  0,
                  MPZ(0),
                  0,
                  0,
              )
              i0 = 0
          else:
              # there is no real part to start (other than the starting 1)
              wre, wim, wre_acc, wim_acc = complex_factors[0]
              acc = min(
                  acc, complex_accuracy((wre, wim, wre_acc, wim_acc))
              )
              re = wre
              im = wim
              i0 = 1
  
          for wre, wim, wre_acc, wim_acc in complex_factors[i0:]:
              # acc is the overall accuracy of the product; we aren't
              # computing exact accuracies of the product.
              acc = min(
                  acc, complex_accuracy((wre, wim, wre_acc, wim_acc))
              )
  
              use_prec = working_prec
              A = mpf_mul(re, wre, use_prec)
              B = mpf_mul(mpf_neg(im), wim, use_prec)
              C = mpf_mul(re, wim, use_prec)
              D = mpf_mul(im, wre, use_prec)
              re = mpf_add(A, B, use_prec)
              im = mpf_add(C, D, use_prec)
          if options.get("verbose"):
              print("MUL: wanted", prec, "accurate bits, got", acc)
          # multiply by I
          if direction & 1:
              re, im = mpf_neg(im), re
          return re, im, acc, acc
  
imports: from __future__ import annotations
  from typing import Tuple, Optional, Union, Callable, List, Dict, Type, TYPE_CHECKING, Any, overload
  import math
  import mpmath.libmp
  from mpmath import make_mpc, make_mpf, mp, mpc, mpf, nsum, quadts, quadosc, workprec
  from mpmath import inf
  from mpmath.libmp import from_int, from_man_exp, from_rational, fhalf, fnan, finf, fninf, fnone, fone, fzero, mpf_abs, mpf_add, mpf_atan, mpf_atan2, mpf_cmp, mpf_cos, mpf_e, mpf_exp, mpf_log, mpf_lt, mpf_mul, mpf_neg, mpf_pi, mpf_pow, mpf_pow_int, mpf_shift, mpf_sin, mpf_sqrt, normalize, round_nearest, to_int, to_str
  from mpmath.libmp import bitcount
  from mpmath.libmp.backend import MPZ
  from mpmath.libmp.libmpc import _infs_nan
  from mpmath.libmp.libmpf import dps_to_prec, prec_to_dps
  from .sympify import sympify
  from .singleton import S
  from sympy.external.gmpy import SYMPY_INTS
  from sympy.utilities.iterables import is_sequence
  from sympy.utilities.lambdify import lambdify
  from sympy.utilities.misc import as_int
label_desc: Use image node_id 25 for calling a global function with example usage: evalf_mul(v, prec, options) and returns: None,  im,  None,  im_acc, evalf, None,  None,  None,  None, re,  im,  acc,  acc, fnan,  None,  prec,  None, fnan,  None,  prec,  None, None,  v,  None,  acc, v,  None,  acc,  None, None,  None,  None,  None