from typing import Callable
import math


def _geometric_binary_search(
        func: Callable[[float], float],
        target: float,
        iterations: int = 24,
        reverse: bool = False
) -> float:
    """Perform a binary search using geometric centers.

    Do a binary search to find the value ``n`` that makes the function ``func``
    return ``target`` when ``n`` is used as the argument. By default, it is
    assumed that smaller values of ``n`` will cause ``func`` to produce smaller
    outputs. If smaller values of ``n`` produce larger outputs, set ``reverse``
    to True.

    This implementation of binary search uses the geometric mean instead of the
    arithmetic mean to determine the center of the search space. This is
    because the values that are being searched are weighted towards zero.

    :param func: A Callable which accepts a float and returns a float. This
        must be a one-to-one function.
    :param target: A float representing the target output which we are trying
        to make func produce.
    :param iterations: An integer representing the number of iterations to run
        the binary search. The default of 24 should be sufficient for most
        applications.
    :param reverse: A bool representing the relationship between the input and
        output values of func.
    :return: A float representing value n which makes the function func produce
        target when called as its argument.
    """

    lower_bound = 2 ** -iterations
    upper_bound = 2 ** iterations
    assert lower_bound <= upper_bound

    for _ in range(iterations):
        guess = math.sqrt(lower_bound * upper_bound)
        answer = func(guess)

        if (not reverse and answer > target) or (reverse and answer < target):
            upper_bound = guess
        else:
            lower_bound = guess

    return math.sqrt(lower_bound * upper_bound)