flan/test/programs/math2.flan
Joseph Ferano b0bc40ca05 atan2 and pow go out to libm, and clamp is a macro rather than four functions
The two declares inherit the sin/cos caveat in full and not the sqrt one:
IEEE-754 requires nothing of atan2f or powf either, so they are the third and
fourth places in the prelude where native and wasm32 may differ in the last
bit. Every case in math2.flan is therefore a value that is exact in binary --
a quadrant boundary, a power of two, a perfect square -- rather than one that
would pin a particular libm and then fail on wasi.

clamp is the interesting one. The prelude already argued against wrapping
(min hi (max lo x)) in a function, and that argument gets stronger rather
than weaker: min and max are builtins at every numeric type and there are no
generics, so a clamp *function* is one copy per type. A macro is
type-agnostic for free and emits nothing at all. The test calls the same
three words at i32, i64, u8 and f32 to show it, and counts evaluations to
show that each argument appears once -- the shape that names x twice reads
identically and calls it twice.

lo above hi answers hi and is not checked. A macro has no error facility, so
the only diagnostic available would be a run-time one, in the construct whose
whole point is that it costs nothing at run time.
2026-09-12 21:48:37 +07:00

79 lines
3.2 KiB
Plaintext

;;;; 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)