The !-means-mutates convention distinguished nothing — there is no immutable counterpart to contrast with — so every mutating name drops the mark: sort, sort-by, sort-bytes, swap, reverse, append, append-i64, append-f64, encode-rune, split-next, map-remove, map-next, and the test helpers beside them. Two could not simply shed it: map! is map-in-place, because map is the into transform's word and means the non-mutating thing; put! is put-at, because put is the Map builtin. The ?-means-asks convention stays. Dated records keep the old spellings; watch.clj's reset-spies! and the other Clojure names are not ours to rename.
101 lines
4.6 KiB
Plaintext
101 lines
4.6 KiB
Plaintext
;;;; format-f64: a number rendered to a fixed number of decimal places.
|
|
;;;;
|
|
;;;; The runtime's f64->bytes is snprintf "%g" and there is no precision to
|
|
;;;; pass it, so this is the first number formatter in the language that a
|
|
;;;; caller can steer. Every case below is one a plausible wrong version gets
|
|
;;;; wrong, and three of them are the ones that actually ship broken: the
|
|
;;;; carry, where the rounded fraction equals the scale and is not a fraction
|
|
;;;; at all; the zero padding, without which 1.005 prints as "1.5"; and the
|
|
;;;; sign, which belongs to the number and not to its integer part, because
|
|
;;;; -0.5 has an integer part of 0 and 0 carries no sign.
|
|
|
|
(defn show [x f64 p i32] ()
|
|
(let [v (format-f64 x p)]
|
|
(println (string (as-slice v)))
|
|
(free v)))
|
|
|
|
(defn main [] i32
|
|
;; The ordinary cases, and the one %g cannot do at all: 1/60 wanted to two
|
|
;; places is a frame time, and "%g" answers 0.0166667.
|
|
(show 3.14159 2) ; 3.14
|
|
(show 0.0166667 2) ; 0.02
|
|
(show 1234.5 1) ; 1234.5
|
|
(show 2.0 0) ; 2
|
|
(show 2.0 3) ; 2.000
|
|
|
|
;; Rounding is half away from zero at the last digit kept, on both signs.
|
|
(show 0.125 2) ; 0.13
|
|
(show -0.125 2) ; -0.13
|
|
(show 2.5 0) ; 3
|
|
(show -2.5 0) ; -3
|
|
|
|
;; The carry. 0.999995 scaled by 10^5 rounds to exactly 100000, which is the
|
|
;; next integer; without the carry this prints "0.100000".
|
|
(show 0.999995 5) ; 1.00000
|
|
(show 9.99 1) ; 10.0
|
|
(show -9.99 1) ; -10.0
|
|
(show 0.99 0) ; 1
|
|
|
|
;; Zero padding. The fraction of 1.005 at three places is 5, and five digits
|
|
;; is not the same number as 005.
|
|
(show 1.005 3) ; 1.005
|
|
(show 1.0001 4) ; 1.0001
|
|
(show 7.0 6) ; 7.000000
|
|
|
|
;; The sign lives on the number, not on the integer part: both of these have
|
|
;; an integer part of 0, which i64->bytes renders without a sign.
|
|
(show -0.5 2) ; -0.50
|
|
(show -0.004 2) ; -0.00
|
|
|
|
;; -0.0 prints as a plain zero. The sign test is (< x 0.0), which -0.0 fails,
|
|
;; and text is not where the sign of a zero should be read from.
|
|
(show 0.0 2) ; 0.00
|
|
(show -0.0 2) ; 0.00
|
|
|
|
;; Precision is clamped rather than refused, at both ends.
|
|
(show 1.5 -3) ; 2
|
|
(show 1.5 40) ; 1.500000000
|
|
|
|
;; The three inputs with no decimal expansion.
|
|
(show (/ 0.0 0.0) 2) ; nan
|
|
(show (/ 1.0 0.0) 2) ; inf
|
|
(show (/ -1.0 0.0) 2) ; -inf
|
|
|
|
;; The same three through print, which goes to the runtime's %g rather than
|
|
;; to format-f64, and the first of them is here for a reason the other two
|
|
;; are not. A NaN carries a sign bit that no arithmetic chose: LLVM folds
|
|
;; (/ 0.0 0.0) at compile time and answers the positive one, divsd answers
|
|
;; the negative one at run time, and "%g" prints the difference. So this
|
|
;; line printed "nan" through one backend and "-nan" through the other for
|
|
;; the same source, and disagreed with the (show ...) above it inside either
|
|
;; one. flan_f64_to_bytes now renders any NaN unsigned, which is what
|
|
;; format-f64 always did. An infinity still prints signed: there the sign is
|
|
;; the value, and both backends were always agreed about it.
|
|
(print (/ 0.0 0.0)) (println "") ; nan
|
|
(print (/ 1.0 0.0)) (println "") ; inf
|
|
(print (/ -1.0 0.0)) (println "") ; -inf
|
|
|
|
;; Past 9e18 an f64 has no fractional bits and the integer part does not fit
|
|
;; in an i64, so this falls back to %g rather than approximating.
|
|
(show 1e20 2) ; 1e+20
|
|
|
|
;; A large magnitude that does fit, where the fraction is genuinely gone: an
|
|
;; f64 has no bits below 1 up there, so the padding produces the zeros.
|
|
(show 1234567890123.0 2) ; 1234567890123.00
|
|
|
|
;; And the thing it is for: a formatted number inside a built string, which
|
|
;; needs the integer part copied out before the fraction is rendered, because
|
|
;; both come through the runtime's one shared scratch buffer.
|
|
(let [b (vec-new u8)]
|
|
(append (addr b) (bytes "fps "))
|
|
(let [f (format-f64 59.94 1)]
|
|
(append (addr b) (as-slice f))
|
|
(free f))
|
|
(append (addr b) (bytes " / frame "))
|
|
(let [f (format-f64 0.0166667 4)]
|
|
(append (addr b) (as-slice f))
|
|
(free f))
|
|
(println (string (as-slice b))) ; fps 59.9 / frame 0.0167
|
|
(free b))
|
|
0)
|