Tuning a Bare Shaft With Binary Search
A bare shaft — an arrow with no fletching — is a brutally honest instrument. With no feathers to drag the tail back into line, it prints its own spine error straight into the target. Shoot one beside a fletched arrow at 18 m. For a right-handed shooter, if the bare shaft lands left of the fletched group, the shaft is stiff; if it lands right, it’s weak. The plunger button — a spring-loaded pin the arrow rests against on the way past the riser — is my one continuous knob. Wind it firmer and the shaft planes one way off the bow; softer, the other. Somewhere between is the tension where bare and fletched arrows kiss the same hole.
That “somewhere between” is a monotonic signal crossing zero, and I don’t need the physics of the whole travelling S-wave to find it. I only need to know which side of centre I landed on, then halve the interval and shoot again.
fn plane(tension: f64) -> f64 {
// signed bare-shaft deviation at 18 m: <0 left (stiff), >0 right (weak)
2.3 - 0.85 * tension
}
fn tune(mut lo: f64, mut hi: f64) -> f64 {
for _ in 0..40 {
let mid = lo + (hi - lo) / 2.0; // not (lo+hi)/2 — avoids the overflow
if plane(mid) > 0.0 { lo = mid } else { hi = mid }
}
lo + (hi - lo) / 2.0
}
fn main() {
println!("plunger tension: {:.3}", tune(0.0, 5.0)); // 2.706
}
Jon Bentley spent a 1986 Programming Pearls column on exactly the line I flagged in that comment. He’d handed binary search to a room of professional programmers and, by his count, ninety percent shipped a broken one — the classic wound being (lo + hi) / 2 overflowing on a big array. A ten-line algorithm from 1946, still drawing blood forty years later.
Haskell says the same thing as a plain recurrence, guard clauses reading like the range-officer’s calls:
plane :: Double -> Double
plane t = 2.3 - 0.85 * t -- signed deviation at 18 m
tune :: Double -> Double -> Double
tune lo hi
| hi - lo < 1e-6 = mid
| plane mid > 0 = tune mid hi
| otherwise = tune lo mid
where mid = lo + (hi - lo) / 2
main :: IO ()
main = putStrLn $ "plunger tension: " ++ show (tune 0 5)
Both land on ≈2.706. On the actual range I got three honest iterations in before the loop broke down — not because the maths ran out, but because my own release started scattering wider than the spine error I was chasing. The arrow was more consistent than the archer. I’ll bring a fresh set of bare shafts next week and see if a steadier hand narrows the bracket.