wasm_num_float (wasm v0.1.0)
View SourceFloating-point arithmetic over the hybrid representation wasm_num describes.
Erlang cannot hold NaN or Infinity in a float, so every operation is split
into a fast path (both operands finite, result finite) and a slow path that
handles the values Erlang refuses. The fast path is guarded with is_float,
which measured 1.9 ns, faster than the unguarded version because the guard
lets the compiler unbox the operands.
Width is the first argument rather than being split across two modules: the specials, comparisons and NaN propagation rules are identical for f32 and f64, and only rounding differs. Duplicating three hundred lines to save one argument would double the surface for the subtle bugs.
Two rules are worth stating because they are where implementations diverge:
- NaN generation versus propagation. An operation that creates a NaN
from non-NaN operands (
inf - inf,0/0,sqrtof a negative) must produce the canonical NaN. An operation that propagates an operand's NaN may keep its payload. The test suite distinguishes these asnan:canonicalandnan:arithmetic. - f32 rounding via f64 is exact. Computing in double precision and rounding once to single is correctly rounded for add, sub, mul, div and sqrt, because double has more than 2*24+2 bits of significand. That is what makes it safe to reuse Erlang's arithmetic rather than simulating single precision.
Summary
Functions
Magnitude. Clears the sign bit, so it is defined on NaN.
Add. Traps never; every result is a value, including NaN and infinity.
The NaN to produce when an operation has to make one.
Round toward positive infinity.
A signed 32-bit integer as a float. Total: every i32 is representable.
An unsigned 32-bit integer as a float. Total.
A signed 64-bit integer as a float, rounding when it does not fit exactly.
An unsigned 64-bit integer as a float, rounding when it does not fit exactly.
The magnitude of the first with the sign of the second. Defined on NaN and on both zeros.
f64 to f32, rounding to nearest and possibly to infinity.
Divide. Division by zero is infinity or NaN, not a trap: only the integer forms trap.
Equality, as 1 or 0. NaN compares equal to nothing, itself included.
Round toward negative infinity.
Greater or equal, as 1 or 0. False when either side is NaN.
Greater than, as 1 or 0. False when either side is NaN.
Whether the sign bit is set, which is true of -0.0 and of a negative NaN.
Less or equal, as 1 or 0. False when either side is NaN.
Less than, as 1 or 0. False when either side is NaN.
The larger, with the same NaN and signed-zero rules as min/3.
The smaller. NaN wins over any value, and -0.0 wins over +0.0, which is not what erlang:min/2 does.
Multiply.
Inequality, as 1 or 0. True when either side is NaN.
Round to nearest, ties to even. Not erlang:round/1, which rounds halves away from zero.
Flip the sign bit. Not 0 - X, which would lose the sign of zero.
f32 to f64. Exact, and the NaN payload is carried across.
Round a double to the target width, mapping overflow to infinity.
Square root. Negative input is NaN rather than an error.
Subtract.
Round toward zero. Still a float: the integer forms are in wasm_num_trunc.
Types
-type fspecial() :: infinity | neg_infinity | {nan, Sign :: 0 | 1, Payload :: non_neg_integer()}.
-type width() :: 32 | 64.
Functions
Magnitude. Clears the sign bit, so it is defined on NaN.
Add. Traps never; every result is a value, including NaN and infinity.
The NaN to produce when an operation has to make one.
Round toward positive infinity.
A signed 32-bit integer as a float. Total: every i32 is representable.
An unsigned 32-bit integer as a float. Total.
A signed 64-bit integer as a float, rounding when it does not fit exactly.
An unsigned 64-bit integer as a float, rounding when it does not fit exactly.
The magnitude of the first with the sign of the second. Defined on NaN and on both zeros.
f64 to f32, rounding to nearest and possibly to infinity.
Divide. Division by zero is infinity or NaN, not a trap: only the integer forms trap.
Equality, as 1 or 0. NaN compares equal to nothing, itself included.
Round toward negative infinity.
Greater or equal, as 1 or 0. False when either side is NaN.
Greater than, as 1 or 0. False when either side is NaN.
Whether the sign bit is set, which is true of -0.0 and of a negative NaN.
Less or equal, as 1 or 0. False when either side is NaN.
Less than, as 1 or 0. False when either side is NaN.
The larger, with the same NaN and signed-zero rules as min/3.
The smaller. NaN wins over any value, and -0.0 wins over +0.0, which is not what erlang:min/2 does.
Multiply.
Inequality, as 1 or 0. True when either side is NaN.
Round to nearest, ties to even. Not erlang:round/1, which rounds halves away from zero.
Flip the sign bit. Not 0 - X, which would lose the sign of zero.
f32 to f64. Exact, and the NaN payload is carried across.
Round a double to the target width, mapping overflow to infinity.
Erlang's float-to-binary conversion saturates to the infinity bit pattern on
overflow, and wasm_num:f32_from_bits/1 turns that pattern back into the
infinity atom, so overflow needs no explicit range test.
Square root. Negative input is NaN rather than an error.
Subtract.
Round toward zero. Still a float: the integer forms are in wasm_num_trunc.