docs: def register(cls, function=None, name=None):
          """
          Registers a new math function *function* with *name* and returns an :py:class:`Operation`
          instance. A math function expects a :py:class:`Number` as its first argument, followed by
          optional (keyword) arguments. When *name* is *None*, the name of the *function* is used. The
          returned object can be used to set the derivative (similar to *property*). Example:
  
          .. code-block:: python
  
              @ops.register
              def my_op(x):
                  return x * 2 + 1
  
              @my_op.derive
              def my_op(x):
                  return 2
  
              num = Number(5, 2)
              print(num) # -> 5.00 (+2.00, -2.00)
  
              num = ops.my_op(num)
              print(num) # -> 11.00 (+4.00, -4.00)
  
          Please note that there is no need to register *simple* functions as in the particular
          example above as most of them are just composite operations whose derivatives are already
          known.
          """
          def register(function):
              op = Operation(function, name=name)
  
              @functools.wraps(function)
              def wrapper(num, *args, **kwargs):
                  if op.derivative is None:
                      raise Exception("cannot run operation '{}', no derivative registered".format(
                          op.name))
  
                  # ensure we deal with a number instance
                  num = ensure_number(num)
  
                  # apply to the nominal value
                  nominal = op.function(num.nominal, *args, **kwargs)
  
                  # apply to all uncertainties via
                  # unc_f = derivative_f(x) * unc_x
                  x = abs(op.derivative(num.nominal, *args, **kwargs))
                  uncertainties = {}
                  for name in num.uncertainties.keys():
                      up, down = num.get_uncertainty(name)
                      uncertainties[name] = (x * up, x * down)
  
                  # create and return the new number
                  return num.__class__(nominal, uncertainties)
  
              # actual registration
              cls._instances[op.name] = op
              setattr(cls, op.name, staticmethod(wrapper))
  
              return op
  
          if function is None:
              return register
          else:
              return register(function)