Implement IEEE immediates for binary32 and binary64.

Clarify the textual encoding of floating point numbers.

Don't allow decimal floating point since conversion to/from binary can produce
rounding problems on some (buggy) systems.
This commit is contained in:
Jakob Stoklund Olesen
2016-03-31 15:22:23 -07:00
parent e5305c249b
commit 5f706b0a1f
2 changed files with 219 additions and 0 deletions

View File

@@ -212,6 +212,9 @@ indicate the different kinds of immediate operands on an instruction.
signed two's complement integer. Instruction encodings may limit the valid
range.
In the textual format, :type:`imm64` immediates appear as decimal or
hexadecimal literals using the same syntax as C.
.. type:: ieee32
A 32-bit immediate floating point number in the IEEE 754-2008 binary32
@@ -229,6 +232,38 @@ indicate the different kinds of immediate operands on an instruction.
bits of the operand are interpreted as if the SIMD vector was loaded from
memory containing the immediate.
The two IEEE floating point immediate types :type:`ieee32` and :type:`ieee64`
are displayed as hexadecimal floating point literals in the textual IL format.
Decimal floating point literals are not allowed because some computer systems
can round differently when converting to binary. The hexadecimal floating point
format is mostly the same as the one used by C99, but extended to represent all
NaN bit patterns:
Normal numbers
Compatible with C99: ``-0x1.Tpe`` where ``T`` are the trailing
significand bits encoded as hexadecimal, and ``e`` is the unbiased exponent
as a decimal number. :type:`ieee32` has 23 trailing significand bits. They
are padded with an extra LSB to produce 6 hexadecimal digits. This is not
necessary for :type:`ieee64` which has 52 trailing significand bits
forming 13 hexadecimal digits with no padding.
Subnormal numbers
Compatible with C99: ``-0x0.Tpemin`` where ``T`` are the trailing
significand bits encoded as hexadecimal, and ``emin`` is the minimum exponent
as a decimal number.
Infinities
Either ``-Inf`` or ``Inf``.
Quiet NaNs
Quiet NaNs have the MSB of the trailing significand set. If the remaining
bits of the trailing significand are all zero, the value is displayed as
``-qNaN`` or ``qNaN``. Otherwise, ``-qNaN:0xT`` where ``T`` are the
trailing significand bits encoded as hexadecimal.
Signaling NaNs
Displayed as ``-sNaN:0xT``.
Control flow
============

View File

@@ -6,6 +6,7 @@
//! module in the meta language.
use std::fmt::{self, Display, Formatter};
use std::mem;
/// 64-bit immediate integer operand.
///
@@ -36,9 +37,123 @@ impl Display for Imm64 {
}
}
/// An IEEE binary32 immediate floating point value.
///
/// All bit patterns are allowed.
pub struct Ieee32(f32);
/// An IEEE binary64 immediate floating point value.
///
/// All bit patterns are allowed.
pub struct Ieee64(f64);
// Format a floating point number in a way that is reasonably human-readable, and that can be
// converted back to binary without any rounding issues. The hexadecimal formatting of normal and
// subnormal numbers is compatible with C99 and the printf "%a" format specifier. The NaN and Inf
// formats are not supported by C99.
//
// The encoding parameters are:
//
// w - exponent field width in bits
// t - trailing significand field width in bits
//
fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
assert!(w > 0 && w <= 16, "Invalid exponent range");
assert!(1 + w + t <= 64, "Too large IEEE format for u64");
let max_e_bits = (1u64 << w) - 1;
let t_bits = bits & ((1u64 << t) - 1); // Trailing significand.
let e_bits = (bits >> t) & max_e_bits; // Biased exponent.
let sign_bit = (bits >> w + t) & 1;
let bias: i32 = (1 << (w - 1)) - 1;
let e = e_bits as i32 - bias; // Unbiased exponent.
let emin = 1 - bias; // Minimum exponent.
// How many hexadecimal digits are needed for the trailing significand?
let digits = (t + 3) / 4;
// Trailing significand left-aligned in `digits` hexadecimal digits.
let left_t_bits = t_bits << (4 * digits - t);
// All formats share the leading sign.
if sign_bit != 0 {
try!(write!(f, "-"));
}
if e_bits == 0 {
if t_bits == 0 {
// Zero.
write!(f, "0.0")
} else {
// Subnormal.
write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin)
}
} else if e_bits == max_e_bits {
if t_bits == 0 {
// Infinity.
write!(f, "Inf")
} else {
// NaN.
let payload = t_bits & ((1 << (t - 1)) - 1);
if t_bits & (1 << (t - 1)) != 0 {
// Quiet NaN.
if payload != 0 {
write!(f, "qNaN:0x{:x}", payload)
} else {
write!(f, "qNaN")
}
} else {
// Signaling NaN.
write!(f, "sNaN:0x{:x}", payload)
}
}
} else {
// Normal number.
write!(f, "0x1.{0:01$x}p{2}", left_t_bits, digits as usize, e)
}
}
impl Ieee32 {
pub fn new(x: f32) -> Ieee32 {
Ieee32(x)
}
/// Construct Ieee32 immediate from raw bits.
pub fn new_from_bits(x: u32) -> Ieee32 {
Ieee32(unsafe { mem::transmute(x) })
}
}
impl Display for Ieee32 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let bits: u32 = unsafe { mem::transmute(self.0) };
format_float(bits as u64, 8, 23, f)
}
}
impl Ieee64 {
pub fn new(x: f64) -> Ieee64 {
Ieee64(x)
}
/// Construct Ieee64 immediate from raw bits.
pub fn new_from_bits(x: u64) -> Ieee64 {
Ieee64(unsafe { mem::transmute(x) })
}
}
impl Display for Ieee64 {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let bits: u64 = unsafe { mem::transmute(self.0) };
format_float(bits, 11, 52, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{f32, f64};
#[test]
fn format_imm64() {
@@ -50,4 +165,73 @@ mod tests {
assert_eq!(format!("{}", Imm64(0xffff)), "0xffff");
assert_eq!(format!("{}", Imm64(0x10000)), "0x0001_0000");
}
#[test]
fn format_ieee32() {
assert_eq!(format!("{}", Ieee32::new(0.0)), "0.0");
assert_eq!(format!("{}", Ieee32::new(-0.0)), "-0.0");
assert_eq!(format!("{}", Ieee32::new(1.0)), "0x1.000000p0");
assert_eq!(format!("{}", Ieee32::new(1.5)), "0x1.800000p0");
assert_eq!(format!("{}", Ieee32::new(0.5)), "0x1.000000p-1");
assert_eq!(format!("{}", Ieee32::new(f32::EPSILON)), "0x1.000000p-23");
assert_eq!(format!("{}", Ieee32::new(f32::MIN)), "-0x1.fffffep127");
assert_eq!(format!("{}", Ieee32::new(f32::MAX)), "0x1.fffffep127");
// Smallest positive normal number.
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE)),
"0x1.000000p-126");
// Subnormals.
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE / 2.0)),
"0x0.800000p-126");
assert_eq!(format!("{}", Ieee32::new(f32::MIN_POSITIVE * f32::EPSILON)),
"0x0.000002p-126");
assert_eq!(format!("{}", Ieee32::new(f32::INFINITY)), "Inf");
assert_eq!(format!("{}", Ieee32::new(f32::NEG_INFINITY)), "-Inf");
assert_eq!(format!("{}", Ieee32::new(f32::NAN)), "qNaN");
assert_eq!(format!("{}", Ieee32::new(-f32::NAN)), "-qNaN");
// Construct some qNaNs with payloads.
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fc00001)), "qNaN:0x1");
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7ff00001)),
"qNaN:0x300001");
// Signaling NaNs.
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7f800001)), "sNaN:0x1");
assert_eq!(format!("{}", Ieee32::new_from_bits(0x7fa00001)),
"sNaN:0x200001");
}
#[test]
fn format_ieee64() {
assert_eq!(format!("{}", Ieee64::new(0.0)), "0.0");
assert_eq!(format!("{}", Ieee64::new(-0.0)), "-0.0");
assert_eq!(format!("{}", Ieee64::new(1.0)), "0x1.0000000000000p0");
assert_eq!(format!("{}", Ieee64::new(1.5)), "0x1.8000000000000p0");
assert_eq!(format!("{}", Ieee64::new(0.5)), "0x1.0000000000000p-1");
assert_eq!(format!("{}", Ieee64::new(f64::EPSILON)),
"0x1.0000000000000p-52");
assert_eq!(format!("{}", Ieee64::new(f64::MIN)),
"-0x1.fffffffffffffp1023");
assert_eq!(format!("{}", Ieee64::new(f64::MAX)),
"0x1.fffffffffffffp1023");
// Smallest positive normal number.
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE)),
"0x1.0000000000000p-1022");
// Subnormals.
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE / 2.0)),
"0x0.8000000000000p-1022");
assert_eq!(format!("{}", Ieee64::new(f64::MIN_POSITIVE * f64::EPSILON)),
"0x0.0000000000001p-1022");
assert_eq!(format!("{}", Ieee64::new(f64::INFINITY)), "Inf");
assert_eq!(format!("{}", Ieee64::new(f64::NEG_INFINITY)), "-Inf");
assert_eq!(format!("{}", Ieee64::new(f64::NAN)), "qNaN");
assert_eq!(format!("{}", Ieee64::new(-f64::NAN)), "-qNaN");
// Construct some qNaNs with payloads.
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff8000000000001)),
"qNaN:0x1");
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ffc000000000001)),
"qNaN:0x4000000000001");
// Signaling NaNs.
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff0000000000001)),
"sNaN:0x1");
assert_eq!(format!("{}", Ieee64::new_from_bits(0x7ff4000000000001)),
"sNaN:0x4000000000001");
}
}