Skip to content

Commit

Permalink
floats: make reading floats safe
Browse files Browse the repository at this point in the history
This commit modifies the existing read_f32/read_f64 methods to
guarantee their safety by returning NaNs when signaling NaNs are
detected.

Fixes #71
  • Loading branch information
SamWhited authored and BurntSushi committed Jul 8, 2017
1 parent 4a4f640 commit 6136293
Showing 1 changed file with 73 additions and 2 deletions.
75 changes: 73 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,8 @@ pub trait ByteOrder

/// Reads a IEEE754 single-precision (4 bytes) floating point number.
///
/// The return value is always defined; signaling NaN's may be turned into quiet NaN's.
///
/// # Panics
///
/// Panics when `buf.len() < 4`.
Expand All @@ -626,11 +628,13 @@ pub trait ByteOrder
/// ```
#[inline]
fn read_f32(buf: &[u8]) -> f32 {
unsafe { transmute(Self::read_u32(buf)) }
safe_u32_bits_to_f32(Self::read_u32(buf))
}

/// Reads a IEEE754 double-precision (8 bytes) floating point number.
///
/// The return value is always defined; signaling NaN's may be turned into quiet NaN's.
///
/// # Panics
///
/// Panics when `buf.len() < 8`.
Expand All @@ -649,7 +653,7 @@ pub trait ByteOrder
/// ```
#[inline]
fn read_f64(buf: &[u8]) -> f64 {
unsafe { transmute(Self::read_u64(buf)) }
safe_u64_bits_to_f64(Self::read_u64(buf))
}

/// Writes a signed 16 bit integer `n` to `buf`.
Expand Down Expand Up @@ -1155,6 +1159,44 @@ impl ByteOrder for LittleEndian {
}
}

#[inline]
fn safe_u32_bits_to_f32(u: u32) -> f32 {
use core::f32::NAN;

const EXP_MASK: u32 = 0x7F800000;
const FRACT_MASK: u32 = 0x007FFFFF;

if u & EXP_MASK == EXP_MASK && u & FRACT_MASK != 0 {
// While IEEE 754-2008 specifies encodings for quiet NaNs and
// signaling ones, certains MIPS and PA-RISC CPUs treat signaling
// NaNs differently. Therefore, to be safe, we pass a known quiet
// NaN if u is any kind of NaN. The check above only assumes
// IEEE 754-1985 to be valid.
unsafe { transmute(NAN) }

This comment has been minimized.

Copy link
@le-jzr

le-jzr Jul 8, 2017

A minor nit: why is the NAN in a transmute?

This comment has been minimized.

Copy link
@BurntSushi

BurntSushi Jul 8, 2017

Owner

Nice catch. Transcription error. :-) Should be fixed now!

} else {
unsafe { transmute(u) }
}
}

#[inline]
fn safe_u64_bits_to_f64(u: u64) -> f64 {
use core::f64::NAN;

const EXP_MASK: u64 = 0x7FF0000000000000;
const FRACT_MASK: u64 = 0x000FFFFFFFFFFFFF;

if u & EXP_MASK == EXP_MASK && u & FRACT_MASK != 0 {
// While IEEE 754-2008 specifies encodings for quiet NaNs and
// signaling ones, certains MIPS and PA-RISC CPUs treat signaling
// NaNs differently. Therefore, to be safe, we pass a known quiet
// NaN if u is any kind of NaN. The check above only assumes
// IEEE 754-1985 to be valid.
unsafe { transmute(NAN) }
} else {
unsafe { transmute(u) }
}
}

#[cfg(test)]
mod test {
extern crate quickcheck;
Expand Down Expand Up @@ -1632,6 +1674,35 @@ mod test {
let n = LittleEndian::read_uint(&[1, 2, 3, 4, 5, 6, 7, 8], 5);
assert_eq!(n, 0x0504030201);
}

#[test]
fn read_snan() {
use core::f32;
use core::f64;
use core::mem::transmute;

use {ByteOrder, BigEndian, LittleEndian};

let sf = BigEndian::read_f32(&[0xFF, 0x80, 0x00, 0x01]);
let sbits: u32 = unsafe { transmute(sf) };
assert_eq!(sbits, unsafe { transmute(f32::NAN) });
assert_eq!(sf.classify(), ::core::num::FpCategory::Nan);

let df = BigEndian::read_f64(&[0x7F, 0xF0, 0, 0, 0, 0, 0, 0x01]);
let dbits: u64 = unsafe { ::core::mem::transmute(df) };
assert_eq!(dbits, unsafe { transmute(f64::NAN) });
assert_eq!(df.classify(), ::core::num::FpCategory::Nan);

let sf = LittleEndian::read_f32(&[0x01, 0x00, 0x80, 0xFF]);
let sbits: u32 = unsafe { transmute(sf) };
assert_eq!(sbits, unsafe { transmute(f32::NAN) });
assert_eq!(sf.classify(), ::core::num::FpCategory::Nan);

let df = LittleEndian::read_f64(&[0x01, 0, 0, 0, 0, 0, 0xF0, 0x7F]);
let dbits: u64 = unsafe { ::core::mem::transmute(df) };
assert_eq!(dbits, unsafe { transmute(f64::NAN) });
assert_eq!(df.classify(), ::core::num::FpCategory::Nan);
}
}

#[cfg(test)]
Expand Down

0 comments on commit 6136293

Please sign in to comment.