Zulip Chat Archive

Stream: lean4

Topic: Bisection algorithm


Oscar Matemb ⚛️ (Aug 23 2025 at 07:00):

I’ve recently implemented the numerical bisection algorithm in Lean and I’m considering what kinds of properties can be formally established about it. Beyond termination, are there other theorems or invariants that are typically proved in this context?
I’m also interested in perspectives on the design itself: would a refactoring be advisable in order to highlight particular structural features maybe? Also, what would a 'real-valued' formulation of this algorithm look like, and is such a formulation meaningful or useful in practice? I’d appreciate any question, feedback or suggestions!

import Lake.Main
import Lake.Load.Resolve

-- result format (sum type)
inductive BisectionResult where
  | success (root : Float) (iterations : Nat)
  | invalidBounds (reason : String)
  | maxIterationsReached (bestApprox : Float) (iterations : Nat)
  | numericalError (reason : String)

-- values for safe checks
structure BisectionConfig where
  tolerance : Float := 1e-10
  maxIterations : Nat := 1000
  minInterval : Float := 1e-15

-- check if floats have opposite signs
def oppositeSigns (x y : Float) : Bool :=
  (x > 0.0 && y < 0.0) || (x < 0.0 && y > 0.0)

-- check for infinity
def isFinite (x : Float) : Bool :=
  ¬(x.isNaN || x.isInf)

-- f (find roots), a b (left right bounds)
def bisection (f : Float  Float) (a b : Float) (config : BisectionConfig := {} ) : BisectionResult :=
  if ¬(isFinite a && isFinite b) then
    BisectionResult.invalidBounds "bounds must be finite numbers"
  else if a >= b then
    BisectionResult.invalidBounds "left bound must be less than right bound"
  else
    let fa := f a
    let fb := f b

    if ¬(isFinite fa && isFinite fb) then
      BisectionResult.numericalError "Function values at bounds are not finite"
    else if fa.abs < config.tolerance then
      BisectionResult.success a 0
    else if fb.abs < config.tolerance then
      BisectionResult.success b 0
    else if ¬(oppositeSigns fa fb) then
      BisectionResult.invalidBounds "Function must have opposite signs at bounds"
    else
      -- left right -> dataset split, iter (iterations done to finding root, not useful?)
      let rec bisectionLoop (left right : Float) (iter : Nat) : BisectionResult :=
        if iter >= config.maxIterations then
          BisectionResult.maxIterationsReached ((left + right) / 2.0) iter
        else if right - left < config.minInterval then
          BisectionResult.numericalError "interval too small for float precision" /-central limit theorem (for larger data)-/
        else
          let mid := (left + right) / 2.0

          if ¬(isFinite mid) then
            BisectionResult.numericalError "midpoint exceeded acceptable limit"
          else
            let fmid := f mid

            if ¬(isFinite fmid) then
              BisectionResult.numericalError s!"Function value at {mid} is not finite"
            else if fmid.abs < config.tolerance then -- count iterations after all infinity checks
              BisectionResult.success mid (iter + 1)
            else if right - left < config.tolerance then
              BisectionResult.success mid (iter + 1)
            else
              let fleft := f left
              if oppositeSigns fleft fmid then
                bisectionLoop left mid (iter + 1)
              else
                bisectionLoop mid right (iter + 1)

      bisectionLoop a b 0


def findRoot (f : Float  Float) (a b : Float) : BisectionResult :=
  bisection f a b

/- 'prettier' display of result -/
def BisectionResult.toString : BisectionResult  String
  | success root iter => s!"Root found: {root} (after {iter} iterations)"
  | invalidBounds reason => s!"Invalid bounds: {reason}"
  | maxIterationsReached approx iter => s!"Max iterations reached: best approximation {approx} (after {iter} iterations)"
  | numericalError reason => s!"Numerical error: {reason}"

/-
instance : ToString BisectionResult where
  toString := BisectionResult.toString -/

Ruben Van de Velde (Aug 23 2025 at 12:15):

Unfortunately, what you can prove about floats is pretty much nothing

Oscar Matemb ⚛️ (Aug 31 2025 at 21:23):

Got it. What critics can you give about the code's structure? Any aspect of it could be refactored in some way maybe?


Last updated: Dec 20 2025 at 21:32 UTC