;;;; atan2, pow, and clamp — the three gaps NEXT.md's second-tier list names ;;;; under "maths gaps". ;;;; ;;;; Two of them are declares over libm and the third is a macro, and that ;;;; split is the whole content of this file. atan2f and powf are not correctly ;;;; rounded under IEEE-754, exactly as sinf and cosf are not, so every case ;;;; below is a value whose answer is exact in binary — a quadrant boundary, a ;;;; power of two, a perfect square — rather than one that would pin a ;;;; particular libm's last bit and then fail on wasi-libc. ;;;; ;;;; clamp is a macro because a function could not be: min and max are builtins ;;;; at every numeric type and there are no generics, so a clamp function is ;;;; one copy per type. The proof that the macro is not one copy per type is ;;;; that the same three-word call below is made at i32, i64, u8 and f32. (defn show [x f32] (print x) (print " ")) ;;; clamp must evaluate each of its three arguments exactly once. A macro that ;;; repeated one — say (if (< x lo) lo (if (> x hi) hi x)), which names x twice ;;; — would read identically and call this twice. (defvar calls i32 0) (defn tick [x i32] i32 (set calls (+ calls 1)) x) (defn main [] i32 ;; atan2 across all four quadrants and on both axes, which is the whole ;; reason for it over a division: the quadrant is recovered from two signs, ;; and (/ y x) has thrown it away before atan sees it. The two on the x = 0 ;; axis are where the division does not exist at all. (show (atan2-f32 1.0 1.0)) ; pi/4 (show (atan2-f32 1.0 -1.0)) ; 3pi/4 (show (atan2-f32 -1.0 -1.0)) ; -3pi/4 (show (atan2-f32 -1.0 1.0)) ; -pi/4 (show (atan2-f32 0.0 1.0)) ; 0 (show (atan2-f32 1.0 0.0)) ; pi/2 (show (atan2-f32 -1.0 0.0)) ; -pi/2 (println "") ;; pow. A power of two, a half-power that is a perfect square, a negative ;; exponent, and the two edge exponents every implementation special-cases. (show (pow-f32 2.0 10.0)) ; 1024 (show (pow-f32 9.0 0.5)) ; 3 (show (pow-f32 2.0 -2.0)) ; 0.25 (show (pow-f32 5.0 0.0)) ; 1 (show (pow-f32 5.0 1.0)) ; 5 (println "") ;; clamp: below the range, above it, and inside it untouched. (print (clamp 0 1 3)) (print " ") ; 1 (print (clamp 5 1 3)) (print " ") ; 3 (print (clamp 2 1 3)) (print " ") ; 2 ;; Both ends are inclusive, which is the off-by-one a hand-written clamp ;; gets wrong with a < where it wanted <=. (print (clamp 1 1 3)) (print " ") ; 1 (print (clamp 3 1 3)) ; 3 (println "") ;; The same three words at three more types, none of which a function could ;; have served without a copy of its own. (print (clamp (i64 900) (i64 0) (i64 255))) (print " ") ; 255 (print (clamp (u8 200) (u8 0) (u8 255))) (print " ") ; 200 (show (clamp 2.5 0.0 1.0)) ; 1 (println "") ;; lo above hi is not an error and answers hi: the outer min wins. Written ;; down here rather than left for someone to discover. (print (clamp 7 5 1)) (println "") ;; Three arguments, three evaluations. (print (clamp (tick 5) (tick 1) (tick 3))) (print " ") (print calls) (println "") 0)