import math


def binomial_test(k: int, n: int) -> float:
  """Calculates a two-tailed binomial test with p=0.5, without scipy.

  Since the expected probability is 0.5, it simplifies a few things:
  1) (0.5**x)*(0.5**(n-x)) = (0.5**n)
  2) A two-tailed test is simply doubling when p = 0.5.
  Scipy is much larger than Nucleus, so this avoids adding it as a dependency.

  Args:
    k: Number of "successes", in this case, the number of supporting reads.
    n: Number of "trials", in this case, the total number of reads.

  Returns:
    The p-value for the binomial test.
  """
  if not k <= n:
    raise ValueError('k must be <= n')
  if k == n / 2:
    return 1.0
  sum_of_ps = 0

  # With p=0.5, the distribution is symmetric, allowing this simplification:
  k = min(k, n - k)
  # Add up all the exact probabilities for each scenario more extreme than k.
  for x in range(0, k + 1):
    # After python 3.8, the following line can be replaced using math.comb.
    n_choose_x = math.factorial(n) / math.factorial(x) / math.factorial(n - x)
    p_for_i = n_choose_x * (0.5**n)
    sum_of_ps += p_for_i
  return sum_of_ps * 2  # Doubling because it's a two-tailed test.