prelude.Float

Part of prelude.

let Float.abs (a: Float): Float

The absolute value. abs of NaN is NaN.

let Float.min (a: Float) (b: Float): Float

The smaller of two. NaN does not compare, so a NaN argument gives an answer that is not meaningful.

let Float.max (a: Float) (b: Float): Float

The larger of two. See Float.min on NaN.

let Float.clamp (x: Float) (lo: Float) (hi: Float): Float

x held between lo and hi inclusive.

let Float.to_fixed (x: Float) (decimals: Int): String

Exactly decimals places after the point, rounded HALF TO EVEN.

--- Why the rounding is done here and not by the C library ---

snprintf("%.*f") is correctly rounded on every platform and does not give the same answer on all of them. glibc and Microsoft's CRT agree on every value whose expansion is not an exact tie (2.675 is really 2.674999..., and both render it 2.67) and disagree on ties: glibc rounds 0.5, 1.5, 2.5, 3.5 to 0, 2, 2, 4 and the Microsoft CRT to 1, 2, 3, 4.

A language whose programs print different numbers depending on the machine has a worse problem than one whose rounding is a line longer, so the decision is made here: the library is asked for a high-precision expansion, and the rounding is applied to the digits.

--- Why half to EVEN ---

It is IEEE 754's default (roundTiesToEven), which is what glibc inherits from the ambient rounding mode rather than choosing, and it is unbiased: rounding every tie away from zero accumulates. Summing the thousand ties 0.5 .. 999.5 gives 500,500 rounded away and 500,000 rounded to even, against an exact 500,000.

Float.round rounds half AWAY from zero and that is not an inconsistency. C specifies round() that way regardless of the FP mode, because it is arithmetic: "the nearest integer, ties away", while this is rendering. Every language with both has this pair.

--- Why 18 digits is enough to tell a tie from a near-miss ---

A tie has to be exact. The gap between neighbouring doubles near any value whose fractional digits matter is enormously larger than 1e-18: next to 2.5 it is about 4.4e-16, so the value after 2.5 renders as 2.500000000000000444 and is visibly not a tie. Above about 1e3 the fraction is all zeros anyway.

let Float.sqrt (x: Float): Float

The square root. Negative input gives NaN rather than failing.

let Float.pow (base: Float) (exp: Float): Float

base raised to exp. A fractional exponent of a negative base gives NaN.

let Float.floor (x: Float): Float

The largest whole number no greater than x — toward negative infinity, so floor(-2.5) is -3.0.

let Float.ceil (x: Float): Float

The smallest whole number no less than x.

let Float.round (x: Float): Float

The nearest whole number, halves AWAY FROM ZERO: round(2.5) is 3.0 and round(-2.5) is -3.0.

Float.to_fixed rounds halves to EVEN, which is not an inconsistency — it is what C, Python, Rust and Java all do, because rounding is arithmetic and formatting is rendering.

let Float.random (): Float

A float in [0.0, 1.0) from a process-wide generator seeded from the clock.

Different every run, and NOT reproducible. Rng.from_seed is the one for a test or a replay.

let Float.random_range (lo: Float) (hi: Float): Float

A float in [lo, hi), from the same unseeded generator as Float.random.

let Float.sin (x: Float): Float

--- Trigonometry, logs and constants (issue #20) ---

Angles are RADIANS everywhere, which is what libm takes and what the inverse functions return. Float.radians and Float.degrees convert at the edges, so a program that thinks in degrees converts once rather than scattering * 3.14159 / 180.0. Sine of an angle in RADIANS.

let Float.cos (x: Float): Float

Cosine of an angle in radians.

let Float.tan (x: Float): Float

Tangent of an angle in radians. Grows without bound near odd multiples of pi/2 rather than failing.

let Float.asin (x: Float): Float

The angle in radians whose sine is x, in [-pi/2, pi/2]. NaN outside [-1, 1].

let Float.acos (x: Float): Float

The angle in radians whose cosine is x, in [0, pi]. NaN outside [-1, 1].

let Float.atan (x: Float): Float

The angle in radians whose tangent is x. Float.atan2 is the one that knows which quadrant.

let Float.atan2 (y: Float) (x: Float): Float

atan2(y, x), in that order, matching libm, Go, Python and Java all use, and the reason it exists: it knows which QUADRANT the point is in, which atan(y / x) cannot, and it does not divide by zero when x is 0.

let Float.log (x: Float): Float

Natural log. log2 and log10 are separate rather than log(x) / log(2.0), because libm computes them directly and the division loses accuracy at exactly the powers people check. The natural logarithm. NaN for a negative argument, and negative infinity at zero.

let Float.log2 (x: Float): Float

Base-2 logarithm, computed directly rather than as a division — which is what keeps log2(8.0) exactly 3.0.

let Float.log10 (x: Float): Float

Base-10 logarithm, computed directly. See Float.log2.

let Float.exp (x: Float): Float

e raised to x.

let Float.pi (): Float

Constants as FUNCTIONS, because Float. is a type namespace and T.f(x) is how a name gets into it. Written out to the last digit a double can hold rather than computed, so they are exact literals and not a rounding of something else. Pi, to the last digit a double holds.

let Float.tau (): Float

Two pi — a full turn, which is the more useful constant when working with angles.

let Float.e (): Float

Euler's number.

let Float.radians (degrees: Float): Float

Degrees to radians. Every trigonometric function here takes radians, so this is the conversion at the edge.

let Float.degrees (radians: Float): Float

Radians to degrees.