;;;; The prelude's type limits, checked against something other than themselves. ;;;; ;;;; A wrong constant here would compile. That is the whole reason this program ;;;; exists: i32-max off by one, or f64-max one ulp low, is a number the ;;;; compiler has no opinion about, and it would sit in the prelude being ;;;; subtly wrong in every program that read it. So nothing below asserts a ;;;; constant against the way it is spelled in the prelude. ;;;; ;;;; The integers are checked by printing them. The expected output beside this ;;;; program in test_acceptance is the decimal spelling of each limit, written ;;;; out independently, and an integer's decimal rendering is exact — so the ;;;; comparison is the whole value and not an approximation of it. u64-max is ;;;; the one that matters most: it is written in hex in the prelude, because ;;;; the reader cannot take its decimal, and this is where that hex is read ;;;; back as the number it is supposed to name. ;;;; ;;;; The floats cannot be checked that way, because printing one is snprintf ;;;; "%g" and that is six significant digits — 3.40282e+38 is equally true of ;;;; f32-max and of a dozen values around it. So each is *derived* here by ;;;; exact power-of-two arithmetic and compared for equality. Every step of ;;;; that derivation is exact in IEEE-754: doubling and halving a float only ;;;; moves the exponent, and the one multiplication that is not a power of two ;;;; has both operands representable and a representable product. The ;;;; derivations are therefore a second, independent construction of the same ;;;; bit pattern, which is what a pin needs to be. ;; 2^n, built by repeated doubling from 1.0 and reciprocated for a negative n. ;; Exact for every n this program asks for: the largest is 2^1023, which is ;; half of f64-max and so is nowhere near overflowing, and the smallest is ;; 2^-1022, whose reciprocal partner 2^1022 is a normal value — so no step ;; passes through a subnormal, where the halving would start losing bits. (defn p2-f64 [n i32] f64 (let [m (if (< n 0) (- 0 n) n) x 1.0] (dotimes [i m] (set x (* x 2.0))) (if (< n 0) (/ 1.0 x) x))) ;; The same, at f32's width and with f32's exponent range. 2^127 is the ;; largest normal power of two an f32 holds and 2^-126 the smallest, and both ;; are exactly the ends this file asks for. (defn p2-f32 [n i32] f32 (let [m (if (< n 0) (- 0 n) n) x (f32 1.0)] (dotimes [i m] (set x (* x (f32 2.0)))) (if (< n 0) (/ (f32 1.0) x) x))) ;; An infinity is a value that equals its own double and is not zero — the ;; same test format-f64 in the prelude uses, and the only one available with ;; no infinity literal to compare against. (defn inf-f64? [x f64] bool (and (= x (* x 2.0)) (!= x 0.0))) (defn inf-f32? [x f32] bool (and (= x (* x (f32 2.0))) (!= x (f32 0.0)))) (defn say [name string ok bool] () (print name) (print " ") (println (if ok "ok" "WRONG"))) (defn main [] i32 ;; The integers, each printed as the exact decimal the expected output pins. (println i8-max) (println i8-min) (println i16-max) (println i16-min) (println i32-max) (println i32-min) (println i64-max) (println i64-min) (println u8-max) (println u8-min) (println u16-max) (println u16-min) (println u32-max) (println u32-min) (println u64-max) (println u64-min) ;; The floats, each against its derivation. ;; ;; An epsilon is the gap from 1.0 to the next value above it, which is ;; 2^-(mantissa bits): 23 for an f32, 52 for an f64. Derived that way here, ;; and then confirmed by the property the name actually promises — adding it ;; to 1.0 moves, adding half of it does not. (say "f32-epsilon" (= f32-epsilon (p2-f32 -23))) (say "f64-epsilon" (= f64-epsilon (p2-f64 -52))) (say "f32-epsilon is the step above 1.0" (and (!= (+ (f32 1.0) f32-epsilon) (f32 1.0)) (= (+ (f32 1.0) (/ f32-epsilon (f32 2.0))) (f32 1.0)))) (say "f64-epsilon is the step above 1.0" (and (!= (+ 1.0 f64-epsilon) 1.0) (= (+ 1.0 (/ f64-epsilon 2.0)) 1.0))) ;; The smallest positive *normal* value is 2^(1-bias): 2^-126 and 2^-1022. ;; Halving one leaves the normals, so the value below it is not simply half ;; — that is the property that says this is the boundary and not some value ;; near it. (say "f32-min-positive" (= f32-min-positive (p2-f32 -126))) (say "f64-min-positive" (= f64-min-positive (p2-f64 -1022))) ;; The greatest finite value is (2 - 2^-mantissa) * 2^maxexp. Both factors ;; are exactly representable and so is the product, which is why this ;; derivation is an equality and not a near-miss. Doubling it overflows to ;; an infinity, which is the other end of the same claim: there is nothing ;; finite above it. (say "f32-max" (= f32-max (* (p2-f32 127) (- (f32 2.0) (p2-f32 -23))))) (say "f64-max" (= f64-max (* (p2-f64 1023) (- 2.0 (p2-f64 -52))))) (say "f32-max is the last finite f32" (inf-f32? (* f32-max (f32 2.0)))) (say "f64-max is the last finite f64" (inf-f64? (* f64-max 2.0))) ;; And the two the language does not need a constant for, said once so that ;; the absence is recorded rather than merely unmentioned: a float's least ;; value is the negation of its greatest, and there is nothing to derive. (say "f32's least value negates its greatest" (< (- (f32 0.0) f32-max) (- (f32 0.0) f32-min-positive))) (say "f64's least value negates its greatest" (< (- 0.0 f64-max) (- 0.0 f64-min-positive))) 0)