Saturating casts between integers and floats (both directions).

This affects regular code generation as well as constant evaluation in trans,
but not the HIR constant evaluator because that one returns an error for
overflowing casts and NaN-to-int casts. That error is conservatively
correct and we should be careful to not accept more code in constant
expressions.
The changes to code generation are guarded by a new -Z flag, to be able
to evaluate the performance impact. The trans constant evaluation changes
are unconditional because they have no run time impact and don't affect
type checking either.
This commit is contained in:
Robin Kruppe 2017-10-09 02:14:00 +02:00
parent 7ade24f672
commit 0d6b52c2f3
10 changed files with 453 additions and 13 deletions

1
src/Cargo.lock generated
View File

@ -1869,6 +1869,7 @@ dependencies = [
"rustc 0.0.0",
"rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_allocator 0.0.0",
"rustc_apfloat 0.0.0",
"rustc_back 0.0.0",
"rustc_const_math 0.0.0",
"rustc_data_structures 0.0.0",

View File

@ -1107,6 +1107,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
"control whether #[inline] functions are in all cgus"),
tls_model: Option<String> = (None, parse_opt_string, [TRACKED],
"choose the TLS model to use (rustc --print tls-models for details)"),
saturating_float_casts: bool = (false, parse_bool, [TRACKED],
"make casts between integers and floats safe: clip out-of-range inputs to the min/max \
integer or to infinity respectively, and turn `NAN` into 0 when casting to integers"),
}
pub fn default_lib_output() -> CrateType {

View File

@ -628,6 +628,8 @@ extern "C" {
pub fn LLVMConstIntGetSExtValue(ConstantVal: ValueRef) -> c_longlong;
pub fn LLVMRustConstInt128Get(ConstantVal: ValueRef, SExt: bool,
high: *mut u64, low: *mut u64) -> bool;
pub fn LLVMRustIsConstantFP(ConstantVal: ValueRef) -> bool;
pub fn LLVMRustConstFloatGetBits(ConstantVal: ValueRef) -> u64;
// Operations on composite constants

View File

@ -19,6 +19,7 @@ owning_ref = "0.3.3"
rustc-demangle = "0.1.4"
rustc = { path = "../librustc" }
rustc_allocator = { path = "../librustc_allocator" }
rustc_apfloat = { path = "../librustc_apfloat" }
rustc_back = { path = "../librustc_back" }
rustc_const_math = { path = "../librustc_const_math" }
rustc_data_structures = { path = "../librustc_data_structures" }

View File

@ -24,6 +24,7 @@
#![feature(custom_attribute)]
#![allow(unused_attributes)]
#![feature(i128_type)]
#![feature(i128)]
#![feature(libc)]
#![feature(quote)]
#![feature(rustc_diagnostic_macros)]
@ -43,6 +44,7 @@ extern crate libc;
extern crate owning_ref;
#[macro_use] extern crate rustc;
extern crate rustc_allocator;
extern crate rustc_apfloat;
extern crate rustc_back;
extern crate rustc_data_structures;
extern crate rustc_incremental;

View File

@ -21,6 +21,7 @@ use rustc::ty::{self, Ty, TyCtxt, TypeFoldable};
use rustc::ty::layout::{self, LayoutTyper};
use rustc::ty::cast::{CastTy, IntTy};
use rustc::ty::subst::{Kind, Substs, Subst};
use rustc_apfloat::{ieee, Float};
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
use {adt, base, machine};
use abi::{self, Abi};
@ -689,20 +690,16 @@ impl<'a, 'tcx> MirConstContext<'a, 'tcx> {
llvm::LLVMConstIntCast(llval, ll_t_out.to_ref(), s)
}
(CastTy::Int(_), CastTy::Float) => {
if signed {
llvm::LLVMConstSIToFP(llval, ll_t_out.to_ref())
} else {
llvm::LLVMConstUIToFP(llval, ll_t_out.to_ref())
}
const_cast_int_to_float(self.ccx, llval, signed, ll_t_out)
}
(CastTy::Float, CastTy::Float) => {
llvm::LLVMConstFPCast(llval, ll_t_out.to_ref())
}
(CastTy::Float, CastTy::Int(IntTy::I)) => {
llvm::LLVMConstFPToSI(llval, ll_t_out.to_ref())
const_cast_from_float(&operand, true, ll_t_out)
}
(CastTy::Float, CastTy::Int(_)) => {
llvm::LLVMConstFPToUI(llval, ll_t_out.to_ref())
const_cast_from_float(&operand, false, ll_t_out)
}
(CastTy::Ptr(_), CastTy::Ptr(_)) |
(CastTy::FnPtr, CastTy::Ptr(_)) |
@ -955,6 +952,51 @@ pub fn const_scalar_checked_binop<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
}
}
unsafe fn const_cast_from_float(operand: &Const, signed: bool, int_ty: Type) -> ValueRef {
let llval = operand.llval;
// Note: this breaks if addresses can be turned into integers (is that possible?)
// But at least an ICE is better than producing undef.
assert!(llvm::LLVMRustIsConstantFP(llval),
"const_cast_from_float: invalid llval {:?}", Value(llval));
let bits = llvm::LLVMRustConstFloatGetBits(llval) as u128;
let int_width = int_ty.int_width() as usize;
let float_bits = match operand.ty.sty {
ty::TyFloat(fty) => fty.bit_width(),
_ => bug!("const_cast_from_float: operand not a float"),
};
// Ignore the Status, to_i128 does the Right Thing(tm) on overflow and NaN even though it
// sets INVALID_OP.
let cast_result = match float_bits {
32 if signed => ieee::Single::from_bits(bits).to_i128(int_width).value as u128,
64 if signed => ieee::Double::from_bits(bits).to_i128(int_width).value as u128,
32 => ieee::Single::from_bits(bits).to_u128(int_width).value,
64 => ieee::Double::from_bits(bits).to_u128(int_width).value,
n => bug!("unsupported float width {}", n),
};
C_big_integral(int_ty, cast_result)
}
unsafe fn const_cast_int_to_float(ccx: &CrateContext,
llval: ValueRef,
signed: bool,
float_ty: Type) -> ValueRef {
// Note: this breaks if addresses can be turned into integers (is that possible?)
// But at least an ICE is better than producing undef.
let value = const_to_opt_u128(llval, signed).unwrap_or_else(|| {
panic!("could not get z128 value of constant integer {:?}",
Value(llval));
});
// If this is an u128 cast and the value is > f32::MAX + 0.5 ULP, round up to infinity.
if signed {
llvm::LLVMConstSIToFP(llval, float_ty.to_ref())
} else if value >= 0xffffff80000000000000000000000000_u128 && float_ty.float_width() == 32 {
let infinity_bits = C_u32(ccx, ieee::Single::INFINITY.to_bits() as u32);
consts::bitcast(infinity_bits, float_ty)
} else {
llvm::LLVMConstUIToFP(llval, float_ty.to_ref())
}
}
impl<'a, 'tcx> MirContext<'a, 'tcx> {
pub fn trans_constant(&mut self,
bcx: &Builder<'a, 'tcx>,

View File

@ -15,11 +15,14 @@ use rustc::ty::layout::{Layout, LayoutTyper};
use rustc::mir::tcx::LvalueTy;
use rustc::mir;
use rustc::middle::lang_items::ExchangeMallocFnLangItem;
use rustc_apfloat::{ieee, Float, Status, Round};
use std::{u128, i128};
use base;
use builder::Builder;
use callee;
use common::{self, val_ty, C_bool, C_i32, C_null, C_usize, C_uint};
use common::{self, val_ty, C_bool, C_i32, C_u32, C_u64, C_null, C_usize, C_uint, C_big_integral};
use consts;
use adt;
use machine;
use monomorphize;
@ -333,14 +336,12 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
bcx.ptrtoint(llval, ll_t_out),
(CastTy::Int(_), CastTy::Ptr(_)) =>
bcx.inttoptr(llval, ll_t_out),
(CastTy::Int(_), CastTy::Float) if signed =>
bcx.sitofp(llval, ll_t_out),
(CastTy::Int(_), CastTy::Float) =>
bcx.uitofp(llval, ll_t_out),
cast_int_to_float(&bcx, signed, llval, ll_t_in, ll_t_out),
(CastTy::Float, CastTy::Int(IntTy::I)) =>
bcx.fptosi(llval, ll_t_out),
cast_float_to_int(&bcx, true, llval, ll_t_in, ll_t_out),
(CastTy::Float, CastTy::Int(_)) =>
bcx.fptoui(llval, ll_t_out),
cast_float_to_int(&bcx, false, llval, ll_t_in, ll_t_out),
_ => bug!("unsupported cast: {:?} to {:?}", operand.ty, cast_ty)
};
OperandValue::Immediate(newval)
@ -815,3 +816,172 @@ fn get_overflow_intrinsic(oop: OverflowOp, bcx: &Builder, ty: Ty) -> ValueRef {
bcx.ccx.get_intrinsic(&name)
}
fn cast_int_to_float(bcx: &Builder,
signed: bool,
x: ValueRef,
int_ty: Type,
float_ty: Type) -> ValueRef {
// Most integer types, even i128, fit into [-f32::MAX, f32::MAX] after rounding.
// It's only u128 -> f32 that can cause overflows (i.e., should yield infinity).
// LLVM's uitofp produces undef in those cases, so we manually check for that case.
let is_u128_to_f32 = !signed && int_ty.int_width() == 128 && float_ty.float_width() == 32;
if is_u128_to_f32 && bcx.sess().opts.debugging_opts.saturating_float_casts {
// f32::MAX + 0.5 ULP as u128. All inputs greater or equal to this should be
// rounded to infinity, for everything else LLVM's uitofp works just fine.
let max = C_big_integral(int_ty, 0xffffff80000000000000000000000000_u128);
let overflow = bcx.icmp(llvm::IntUGE, x, max);
let infinity_bits = C_u32(bcx.ccx, ieee::Single::INFINITY.to_bits() as u32);
let infinity = consts::bitcast(infinity_bits, float_ty);
bcx.select(overflow, infinity, bcx.uitofp(x, float_ty))
} else {
if signed {
bcx.sitofp(x, float_ty)
} else {
bcx.uitofp(x, float_ty)
}
}
}
fn cast_float_to_int(bcx: &Builder,
signed: bool,
x: ValueRef,
float_ty: Type,
int_ty: Type) -> ValueRef {
if !bcx.sess().opts.debugging_opts.saturating_float_casts {
if signed {
return bcx.fptosi(x, int_ty);
} else {
return bcx.fptoui(x, int_ty);
}
}
// LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the
// destination integer type after rounding towards zero. This `undef` value can cause UB in
// safe code (see issue #10184), so we implement a saturating conversion on top of it:
// Semantically, the mathematical value of the input is rounded towards zero to the next
// mathematical integer, and then the result is clamped into the range of the destination
// integer type. Positive and negative infinity are mapped to the maximum and minimum value of
// the destination integer type. NaN is mapped to 0.
//
// Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
// a value representable in int_ty.
// They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
// Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
// int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
// representable. Note that this only works if float_ty's exponent range is sufficently large.
// f16 or 256 bit integers would break this property. Right now the smallest float type is f32
// with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
// On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
// we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
// This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
fn compute_clamp_bounds<F: Float>(signed: bool, int_ty: Type) -> (u128, u128, Status) {
let f_min = if signed {
let int_min = i128::MIN >> (128 - int_ty.int_width());
let rounded_min = F::from_i128_r(int_min, Round::TowardZero);
assert_eq!(rounded_min.status, Status::OK);
rounded_min.value
} else {
F::ZERO
};
let rounded_max = F::from_u128_r(int_max(signed, int_ty), Round::TowardZero);
assert!(rounded_max.value.is_finite());
(f_min.to_bits(), rounded_max.value.to_bits(), rounded_max.status)
}
fn int_max(signed: bool, int_ty: Type) -> u128 {
let shift_amount = 128 - int_ty.int_width();
if signed {
i128::MAX as u128 >> shift_amount
} else {
u128::MAX >> shift_amount
}
}
let (f_min, f_max, f_max_status) = match float_ty.float_width() {
32 => compute_clamp_bounds::<ieee::Single>(signed, int_ty),
64 => compute_clamp_bounds::<ieee::Double>(signed, int_ty),
n => bug!("unsupported float width {}", n),
};
let float_bits_to_llval = |bits| {
let bits_llval = match float_ty.float_width() {
32 => C_u32(bcx.ccx, bits as u32),
64 => C_u64(bcx.ccx, bits as u64),
n => bug!("unsupported float width {}", n),
};
consts::bitcast(bits_llval, float_ty)
};
let f_min = float_bits_to_llval(f_min);
let f_max = float_bits_to_llval(f_max);
// To implement saturation, we perform the following steps (not all steps are necessary for
// all combinations of int_ty and float_ty, but we'll deal with that below):
//
// 1. Clamp x into the range [f_min, f_max] in such a way that NaN becomes f_min.
// 2. If x is NaN, replace the result of the clamping with 0.0, otherwise
// keep the clamping result.
// 3. Now cast the result of step 2 with fpto[su]i.
// 4. If x > f_max, return int_ty::MAX, otherwise return the result of step 3.
//
// This avoids undef because values in range [f_min, f_max] by definition fit into the
// destination type. More importantly, it correctly implements saturating conversion.
// Proof (sketch):
// If x is NaN, step 2 yields 0.0, which is converted to 0 in step 3, and NaN > f_max does
// not hold in step 4, therefore 0 is returned, as desired.
// Otherwise, x is finite or infinite and thus can be compared with f_min and f_max.
// This yields three cases to consider:
// (1) if x in [f_min, f_max], steps 1, 2, and 4 do nothing and the result of fpto[su]i
// is returned, which agrees with saturating conversion for inputs in that range.
// (2) if x > f_max, then x is larger than int_ty::MAX and step 4 correctly returns
// int_ty::MAX. This holds even if f_max is rounded (i.e., if f_max < int_ty::MAX)
// because in those cases, nextUp(f_max) is already larger than int_ty::MAX.
// (3) if x < f_min, then x is smaller than int_ty::MIN and is clamped to f_min. As shown
// earlier, f_min exactly equals int_ty::MIN and therefore no fixup analogous to step 4
// is needed. Instead, step 3 casts f_min to int_ty::MIN and step 4 returns this cast
// result, as desired.
// QED.
// Step 1: Clamping. Computed as:
// clamped_to_min = if f_min < x { x } else { f_min };
// clamped_x = if f_max < clamped_to_min { f_max } else { clamped_to_min };
// Note that for x = NaN, both of the above variables become f_min.
let clamped_to_min = bcx.select(bcx.fcmp(llvm::RealOLT, f_min, x), x, f_min);
let clamped_x = bcx.select(
bcx.fcmp(llvm::RealOLT, f_max, clamped_to_min),
f_max,
clamped_to_min
);
// Step 2: NaN replacement.
// For unsigned types, f_min == 0.0 and therefore clamped_x is already zero.
// Therefore we only need to execute this step for signed integer types.
let clamped_x = if signed {
let zero = match float_ty.float_width() {
32 => float_bits_to_llval(ieee::Single::ZERO.to_bits()),
64 => float_bits_to_llval(ieee::Double::ZERO.to_bits()),
n => bug!("unsupported float width {}", n),
};
// LLVM has no isNaN predicate, so we use (x == x) instead
bcx.select(bcx.fcmp(llvm::RealOEQ, x, x), clamped_x, zero)
} else {
clamped_x
};
// Step 3: fpto[su]i cast
let cast_result = if signed {
bcx.fptosi(clamped_x, int_ty)
} else {
bcx.fptoui(clamped_x, int_ty)
};
// Step 4: f_max fixup.
// Note that x > f_max implies that x was clamped to f_max in step 1, and therefore the
// cast result is the integer equal to f_max. If the conversion from int_ty::MAX to f_max
// was exact, then the result of casting f_max is again int_ty::MAX, so we'd return the same
// value whether or not x > f_max holds. Therefore, we only need to execute this step
// if f_max is inexact.
if f_max_status.contains(Status::INEXACT) {
let int_max = C_big_integral(int_ty, int_max(signed, int_ty));
bcx.select(bcx.fcmp(llvm::RealOGT, x, f_max), int_max, cast_result)
} else {
cast_result
}
}

View File

@ -1373,6 +1373,19 @@ extern "C" bool LLVMRustConstInt128Get(LLVMValueRef CV, bool sext, uint64_t *hig
return true;
}
extern "C" uint64_t LLVMRustConstFloatGetBits(LLVMValueRef CV) {
auto C = unwrap<llvm::ConstantFP>(CV);
APInt Bits = C->getValueAPF().bitcastToAPInt();
if (!Bits.isIntN(64)) {
report_fatal_error("Float bit pattern >64 bits");
}
return Bits.getLimitedValue();
}
extern "C" bool LLVMRustIsConstantFP(LLVMValueRef CV) {
return isa<llvm::ConstantFP>(unwrap<llvm::Value>(CV));
}
extern "C" LLVMContextRef LLVMRustGetValueContext(LLVMValueRef V) {
return wrap(&unwrap(V)->getContext());
}

View File

@ -0,0 +1,65 @@
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -C no-prepopulate-passes
// This file tests that we don't generate any code for saturation if
// -Z saturating-float-casts is not enabled.
#![crate_type = "lib"]
#![feature(i128_type)]
// CHECK-LABEL: @f32_to_u32
#[no_mangle]
pub fn f32_to_u32(x: f32) -> u32 {
// CHECK: fptoui
// CHECK-NOT: fcmp
// CHECK-NOT: icmp
// CHECK-NOT: select
x as u32
}
// CHECK-LABEL: @f32_to_i32
#[no_mangle]
pub fn f32_to_i32(x: f32) -> i32 {
// CHECK: fptosi
// CHECK-NOT: fcmp
// CHECK-NOT: icmp
// CHECK-NOT: select
x as i32
}
#[no_mangle]
pub fn f64_to_u8(x: f32) -> u16 {
// CHECK-NOT: fcmp
// CHECK-NOT: icmp
// CHECK-NOT: select
x as u16
}
// CHECK-LABEL: @i32_to_f64
#[no_mangle]
pub fn i32_to_f64(x: i32) -> f64 {
// CHECK: sitofp
// CHECK-NOT: fcmp
// CHECK-NOT: icmp
// CHECK-NOT: select
x as f64
}
// CHECK-LABEL: @u128_to_f32
#[no_mangle]
pub fn u128_to_f32(x: u128) -> f32 {
// CHECK: uitofp
// CHECK-NOT: fcmp
// CHECK-NOT: icmp
// CHECK-NOT: select
x as f32
}

View File

@ -0,0 +1,141 @@
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -Z saturating-float-casts
#![feature(test, i128, i128_type, stmt_expr_attributes)]
#![deny(overflowing_literals)]
extern crate test;
use std::{f32, f64};
use std::{u8, i8, u16, i16, u32, i32, u64, i64, u128, i128};
use test::black_box;
macro_rules! test {
($val:expr, $src_ty:ident -> $dest_ty:ident, $expected:expr) => (
// black_box disables constant evaluation to test run-time conversions:
assert_eq!(black_box::<$src_ty>($val) as $dest_ty, $expected,
"run time {} -> {}", stringify!($src_ty), stringify!($dest_ty));
// ... whereas this variant triggers constant evaluation:
{
const X: $src_ty = $val;
const Y: $dest_ty = X as $dest_ty;
assert_eq!(Y, $expected,
"const eval {} -> {}", stringify!($src_ty), stringify!($dest_ty));
}
);
($fval:expr, f* -> $ity:ident, $ival:expr) => (
test!($fval, f32 -> $ity, $ival);
test!($fval, f64 -> $ity, $ival);
)
}
macro_rules! common_fptoi_tests {
($fty:ident -> $($ity:ident)+) => ({ $(
test!($fty::NAN, $fty -> $ity, 0);
test!($fty::INFINITY, $fty -> $ity, $ity::MAX);
test!($fty::NEG_INFINITY, $fty -> $ity, $ity::MIN);
// These two tests are not solely float->int tests, in particular the latter relies on
// `u128::MAX as f32` not being UB. But that's okay, since this file tests int->float
// as well, the test is just slightly misplaced.
test!($ity::MIN as $fty, $fty -> $ity, $ity::MIN);
test!($ity::MAX as $fty, $fty -> $ity, $ity::MAX);
test!(0., $fty -> $ity, 0);
test!($fty::MIN_POSITIVE, $fty -> $ity, 0);
test!(-0.9, $fty -> $ity, 0);
test!(1., $fty -> $ity, 1);
test!(42., $fty -> $ity, 42);
)+ });
(f* -> $($ity:ident)+) => ({
common_fptoi_tests!(f32 -> $($ity)+);
common_fptoi_tests!(f64 -> $($ity)+);
})
}
macro_rules! fptoui_tests {
($fty: ident -> $($ity: ident)+) => ({ $(
test!(-0., $fty -> $ity, 0);
test!(-$fty::MIN_POSITIVE, $fty -> $ity, 0);
test!(-0.99999994, $fty -> $ity, 0);
test!(-1., $fty -> $ity, 0);
test!(-100., $fty -> $ity, 0);
test!(#[allow(overflowing_literals)] -1e50, $fty -> $ity, 0);
test!(#[allow(overflowing_literals)] -1e130, $fty -> $ity, 0);
)+ });
(f* -> $($ity:ident)+) => ({
fptoui_tests!(f32 -> $($ity)+);
fptoui_tests!(f64 -> $($ity)+);
})
}
pub fn main() {
common_fptoi_tests!(f* -> i8 i16 i32 i64 i128 u8 u16 u32 u64 u128);
fptoui_tests!(f* -> u8 u16 u32 u64 u128);
// The following tests cover edge cases for some integer types.
// u8
test!(254., f* -> u8, 254);
test!(256., f* -> u8, 255);
// i8
test!(-127., f* -> i8, -127);
test!(-129., f* -> i8, -128);
test!(126., f* -> i8, 126);
test!(128., f* -> i8, 127);
// i32
// -2147483648. is i32::MIN (exactly)
test!(-2147483648., f* -> i32, i32::MIN);
// 2147483648. is i32::MAX rounded up
test!(2147483648., f32 -> i32, 2147483647);
// With 24 significand bits, floats with magnitude in [2^30 + 1, 2^31] are rounded to
// multiples of 2^7. Therefore, nextDown(round(i32::MAX)) is 2^31 - 128:
test!(2147483520., f32 -> i32, 2147483520);
// Similarly, nextUp(i32::MIN) is i32::MIN + 2^8 and nextDown(i32::MIN) is i32::MIN - 2^7
test!(-2147483904., f* -> i32, i32::MIN);
test!(-2147483520., f* -> i32, -2147483520);
// u32 -- round(MAX) and nextUp(round(MAX))
test!(4294967040., f* -> u32, 4294967040);
test!(4294967296., f* -> u32, 4294967295);
// u128
// # float->int
test!(f32::MAX, f32 -> u128, 0xffffff00000000000000000000000000);
// nextDown(f32::MAX) = 2^128 - 2 * 2^104
const SECOND_LARGEST_F32: f32 = 340282326356119256160033759537265639424.;
test!(SECOND_LARGEST_F32, f32 -> u128, 0xfffffe00000000000000000000000000);
// # int->float
// f32::MAX - 0.5 ULP and smaller should be rounded down
test!(0xfffffe00000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32);
test!(0xfffffe7fffffffffffffffffffffffff, u128 -> f32, SECOND_LARGEST_F32);
test!(0xfffffe80000000000000000000000000, u128 -> f32, SECOND_LARGEST_F32);
// numbers within < 0.5 ULP of f32::MAX it should be rounded to f32::MAX
test!(0xfffffe80000000000000000000000001, u128 -> f32, f32::MAX);
test!(0xfffffeffffffffffffffffffffffffff, u128 -> f32, f32::MAX);
test!(0xffffff00000000000000000000000000, u128 -> f32, f32::MAX);
test!(0xffffff00000000000000000000000001, u128 -> f32, f32::MAX);
test!(0xffffff7fffffffffffffffffffffffff, u128 -> f32, f32::MAX);
// f32::MAX + 0.5 ULP and greater should be rounded to infinity
test!(0xffffff80000000000000000000000000, u128 -> f32, f32::INFINITY);
test!(0xffffff80000000f00000000000000000, u128 -> f32, f32::INFINITY);
test!(0xffffff87ffffffffffffffff00000001, u128 -> f32, f32::INFINITY);
test!(!0, u128 -> f32, f32::INFINITY);
// u128->f64 should not be affected by the u128->f32 checks
test!(0xffffff80000000000000000000000000, u128 -> f64,
340282356779733661637539395458142568448.0);
test!(u128::MAX, u128 -> f64, 340282366920938463463374607431768211455.0);
}