bigint: fix float conversions

The default implementations of to_f32, to_f64, from_32 and from_f64 are
limited to numbers fitting in i64 but BigInt can of course be much bigger.
The default to_f32 also has double rounding.
from_f32 and from_f64 have undefined behaviour if the float is too big to
fit in an i64.

This fixes these issues and keeps the rounding consistant with other float
to int conversions.

Also add ToBigUint and ToBigInt implementations for f32 and f64.
This commit is contained in:
Oliver Middleton 2015-12-22 02:20:52 +00:00
parent 283a2e192d
commit a480c7ca6c
1 changed files with 419 additions and 0 deletions

View File

@ -68,11 +68,13 @@ use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub};
use std::str::{self, FromStr};
use std::fmt;
use std::cmp::Ordering::{self, Less, Greater, Equal};
use std::{f32, f64};
use std::{u8, i64, u64};
use rand::Rng;
use traits::{ToPrimitive, FromPrimitive};
use traits::Float;
use {Num, Unsigned, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, Signed, Zero, One};
use self::Sign::{Minus, NoSign, Plus};
@ -1168,6 +1170,71 @@ impl ToPrimitive for BigUint {
_ => None
}
}
// `DoubleBigDigit` size dependent
#[inline]
fn to_f32(&self) -> Option<f32> {
match self.data.len() {
0 => Some(f32::zero()),
1 => Some(self.data[0] as f32),
len => {
// prevent overflow of exponant
if len > (f32::MAX_EXP as usize) / big_digit::BITS {
None
} else {
let exponant = (len - 2) * big_digit::BITS;
// we need 25 significant digits, 24 to be stored and 1 for rounding
// this gives at least 33 significant digits
let mantissa = big_digit::to_doublebigdigit(self.data[len - 1], self.data[len - 2]);
// this cast handles rounding
let ret = (mantissa as f32) * 2.0.powi(exponant as i32);
if ret.is_infinite() {
None
} else {
Some(ret)
}
}
}
}
}
// `DoubleBigDigit` size dependent
#[inline]
fn to_f64(&self) -> Option<f64> {
match self.data.len() {
0 => Some(f64::zero()),
1 => Some(self.data[0] as f64),
2 => Some(big_digit::to_doublebigdigit(self.data[1], self.data[0]) as f64),
len => {
// this will prevent any overflow of exponant
if len > (f64::MAX_EXP as usize) / big_digit::BITS {
None
} else {
let mut exponant = (len - 2) * big_digit::BITS;
let mut mantissa = big_digit::to_doublebigdigit(self.data[len - 1], self.data[len - 2]);
// we need at least 54 significant bit digits, 53 to be stored and 1 for rounding
// so we take enough from the next BigDigit to make it up if needed
let needed = (f64::MANTISSA_DIGITS as usize) + 1;
let bits = (2 * big_digit::BITS) - (mantissa.leading_zeros() as usize);
if needed > bits {
let diff = needed - bits;
mantissa <<= diff;
exponant -= diff;
let mut x = self.data[len - 3];
x >>= big_digit::BITS - diff;
mantissa |= x as u64;
}
// this cast handles rounding
let ret = (mantissa as f64) * 2.0.powi(exponant as i32);
if ret.is_infinite() {
None
} else {
Some(ret)
}
}
}
}
}
}
impl FromPrimitive for BigUint {
@ -1184,6 +1251,41 @@ impl FromPrimitive for BigUint {
fn from_u64(n: u64) -> Option<BigUint> {
Some(BigUint::from(n))
}
#[inline]
fn from_f32(n: f32) -> Option<BigUint> {
BigUint::from_f64(n as f64)
}
#[inline]
fn from_f64(mut n: f64) -> Option<BigUint> {
// handle NAN, INFINITY, NEG_INFINITY
if !n.is_finite() {
return None;
}
// match the rounding of casting from float to int
n = n.trunc();
// handle 0.x, -0.x
if n.is_zero() {
return Some(BigUint::zero());
}
let (mantissa, exponent, sign) = Float::integer_decode(n);
if sign == -1 {
return None;
}
let mut ret = BigUint::from(mantissa);
if exponent > 0 {
ret = ret << exponent as usize;
} else if exponent < 0 {
ret = ret >> (-exponent) as usize;
}
Some(ret)
}
}
impl From<u64> for BigUint {
@ -1261,6 +1363,8 @@ impl_to_biguint!(u8, FromPrimitive::from_u8);
impl_to_biguint!(u16, FromPrimitive::from_u16);
impl_to_biguint!(u32, FromPrimitive::from_u32);
impl_to_biguint!(u64, FromPrimitive::from_u64);
impl_to_biguint!(f32, FromPrimitive::from_f32);
impl_to_biguint!(f64, FromPrimitive::from_f64);
// Extract bitwise digits that evenly divide BigDigit
fn to_bitwise_digits_le(u: &BigUint, bits: usize) -> Vec<u8> {
@ -2123,6 +2227,16 @@ impl ToPrimitive for BigInt {
Minus => None
}
}
#[inline]
fn to_f32(&self) -> Option<f32> {
self.data.to_f32().map(|n| if self.sign == Minus { -n } else { n })
}
#[inline]
fn to_f64(&self) -> Option<f64> {
self.data.to_f64().map(|n| if self.sign == Minus { -n } else { n })
}
}
impl FromPrimitive for BigInt {
@ -2135,6 +2249,24 @@ impl FromPrimitive for BigInt {
fn from_u64(n: u64) -> Option<BigInt> {
Some(BigInt::from(n))
}
#[inline]
fn from_f32(n: f32) -> Option<BigInt> {
if n >= 0.0 {
BigUint::from_f32(n).map(|x| BigInt::from_biguint(Plus, x))
} else {
BigUint::from_f32(-n).map(|x| BigInt::from_biguint(Minus, x))
}
}
#[inline]
fn from_f64(n: f64) -> Option<BigInt> {
if n >= 0.0 {
BigUint::from_f64(n).map(|x| BigInt::from_biguint(Plus, x))
} else {
BigUint::from_f64(-n).map(|x| BigInt::from_biguint(Minus, x))
}
}
}
impl From<i64> for BigInt {
@ -2248,6 +2380,8 @@ impl_to_bigint!(u8, FromPrimitive::from_u8);
impl_to_bigint!(u16, FromPrimitive::from_u16);
impl_to_bigint!(u32, FromPrimitive::from_u32);
impl_to_bigint!(u64, FromPrimitive::from_u64);
impl_to_bigint!(f32, FromPrimitive::from_f32);
impl_to_bigint!(f64, FromPrimitive::from_f64);
pub trait RandBigInt {
/// Generate a random `BigUint` of the given bit size.
@ -2544,6 +2678,7 @@ mod biguint_tests {
use super::Sign::Plus;
use std::cmp::Ordering::{Less, Equal, Greater};
use std::{f32, f64};
use std::i64;
use std::iter::repeat;
use std::str::FromStr;
@ -2552,6 +2687,7 @@ mod biguint_tests {
use rand::thread_rng;
use {Num, Zero, One, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv};
use {ToPrimitive, FromPrimitive};
use Float;
/// Assert that an op works for all val/ref combinations
macro_rules! assert_op {
@ -3031,6 +3167,137 @@ mod biguint_tests {
assert_eq!(BigUint::new(vec!(N1, N1, N1)).to_u64(), None);
}
#[test]
fn test_convert_f32() {
fn check(b1: &BigUint, f: f32) {
let b2 = BigUint::from_f32(f).unwrap();
assert_eq!(b1, &b2);
assert_eq!(b1.to_f32().unwrap(), f);
}
check(&BigUint::zero(), 0.0);
check(&BigUint::one(), 1.0);
check(&BigUint::from(u16::MAX), 2.0.powi(16) - 1.0);
check(&BigUint::from(1u64 << 32), 2.0.powi(32));
check(&BigUint::from_slice(&[0, 0, 1]), 2.0.powi(64));
check(&((BigUint::one() << 100) + (BigUint::one() << 123)), 2.0.powi(100) + 2.0.powi(123));
check(&(BigUint::one() << 127), 2.0.powi(127));
check(&(BigUint::from((1u64 << 24) - 1) << (128 - 24)), f32::MAX);
// keeping all 24 digits with the bits at different offsets to the BigDigits
let x: u32 = 0b00000000101111011111011011011101;
let mut f = x as f32;
let mut b = BigUint::from(x);
for _ in 0..64 {
check(&b, f);
f *= 2.0;
b = b << 1;
}
// this number when rounded to f64 then f32 isn't the same as when rounded straight to f32
let n: u64 = 0b0000000000111111111111111111111111011111111111111111111111111111;
assert!((n as f64) as f32 != n as f32);
assert_eq!(BigUint::from(n).to_f32(), Some(n as f32));
// test rounding up with the bits at different offsets to the BigDigits
let mut f = ((1u64 << 25) - 1) as f32;
let mut b = BigUint::from(1u64 << 25);
for _ in 0..64 {
assert_eq!(b.to_f32(), Some(f));
f *= 2.0;
b = b << 1;
}
// rounding
assert_eq!(BigUint::from_f32(-1.0), None);
assert_eq!(BigUint::from_f32(-0.99999), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(-0.5), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(-0.0), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(f32::MIN_POSITIVE / 2.0), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(f32::MIN_POSITIVE), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(0.5), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(0.99999), Some(BigUint::zero()));
assert_eq!(BigUint::from_f32(f32::consts::E), Some(BigUint::from(2u32)));
assert_eq!(BigUint::from_f32(f32::consts::PI), Some(BigUint::from(3u32)));
// special float values
assert_eq!(BigUint::from_f32(f32::NAN), None);
assert_eq!(BigUint::from_f32(f32::INFINITY), None);
assert_eq!(BigUint::from_f32(f32::NEG_INFINITY), None);
assert_eq!(BigUint::from_f32(f32::MIN), None);
// largest BigUint that will round to a finite f32 value
let big_num = (BigUint::one() << 128) - BigUint::one() - (BigUint::one() << (128 - 25));
assert_eq!(big_num.to_f32(), Some(f32::MAX));
assert_eq!((big_num + BigUint::one()).to_f32(), None);
assert_eq!(((BigUint::one() << 128) - BigUint::one()).to_f32(), None);
assert_eq!((BigUint::one() << 128).to_f32(), None);
}
#[test]
fn test_convert_f64() {
fn check(b1: &BigUint, f: f64) {
let b2 = BigUint::from_f64(f).unwrap();
assert_eq!(b1, &b2);
assert_eq!(b1.to_f64().unwrap(), f);
}
check(&BigUint::zero(), 0.0);
check(&BigUint::one(), 1.0);
check(&BigUint::from(u32::MAX), 2.0.powi(32) - 1.0);
check(&BigUint::from(1u64 << 32), 2.0.powi(32));
check(&BigUint::from_slice(&[0, 0, 1]), 2.0.powi(64));
check(&((BigUint::one() << 100) + (BigUint::one() << 152)), 2.0.powi(100) + 2.0.powi(152));
check(&(BigUint::one() << 1023), 2.0.powi(1023));
check(&(BigUint::from((1u64 << 53) - 1) << (1024 - 53)), f64::MAX);
// keeping all 53 digits with the bits at different offsets to the BigDigits
let x: u64 = 0b0000000000011110111110110111111101110111101111011111011011011101;
let mut f = x as f64;
let mut b = BigUint::from(x);
for _ in 0..128 {
check(&b, f);
f *= 2.0;
b = b << 1;
}
// test rounding up with the bits at different offsets to the BigDigits
let mut f = ((1u64 << 54) - 1) as f64;
let mut b = BigUint::from(1u64 << 54);
for _ in 0..128 {
assert_eq!(b.to_f64(), Some(f));
f *= 2.0;
b = b << 1;
}
// rounding
assert_eq!(BigUint::from_f64(-1.0), None);
assert_eq!(BigUint::from_f64(-0.99999), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(-0.5), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(-0.0), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(f64::MIN_POSITIVE / 2.0), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(f64::MIN_POSITIVE), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(0.5), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(0.99999), Some(BigUint::zero()));
assert_eq!(BigUint::from_f64(f64::consts::E), Some(BigUint::from(2u32)));
assert_eq!(BigUint::from_f64(f64::consts::PI), Some(BigUint::from(3u32)));
// special float values
assert_eq!(BigUint::from_f64(f64::NAN), None);
assert_eq!(BigUint::from_f64(f64::INFINITY), None);
assert_eq!(BigUint::from_f64(f64::NEG_INFINITY), None);
assert_eq!(BigUint::from_f64(f64::MIN), None);
// largest BigUint that will round to a finite f64 value
let big_num = (BigUint::one() << 1024) - BigUint::one() - (BigUint::one() << (1024 - 54));
assert_eq!(big_num.to_f64(), Some(f64::MAX));
assert_eq!((big_num + BigUint::one()).to_f64(), None);
assert_eq!(((BigInt::one() << 1024) - BigInt::one()).to_f64(), None);
assert_eq!((BigUint::one() << 1024).to_f64(), None);
}
#[test]
fn test_convert_to_bigint() {
fn check(n: BigUint, ans: BigInt) {
@ -3585,6 +3852,7 @@ mod bigint_tests {
use super::Sign::{Minus, NoSign, Plus};
use std::cmp::Ordering::{Less, Equal, Greater};
use std::{f32, f64};
use std::{i8, i16, i32, i64, isize};
use std::iter::repeat;
use std::{u8, u16, u32, u64, usize};
@ -3593,6 +3861,7 @@ mod bigint_tests {
use rand::thread_rng;
use {Zero, One, Signed, ToPrimitive, FromPrimitive, Num};
use Float;
/// Assert that an op works for all val/ref combinations
macro_rules! assert_op {
@ -3793,6 +4062,156 @@ mod bigint_tests {
assert_eq!(BigInt::from_biguint(Minus, BigUint::new(vec!(1, 2, 3, 4, 5))).to_u64(), None);
}
#[test]
fn test_convert_f32() {
fn check(b1: &BigInt, f: f32) {
let b2 = BigInt::from_f32(f).unwrap();
assert_eq!(b1, &b2);
assert_eq!(b1.to_f32().unwrap(), f);
let neg_b1 = -b1;
let neg_b2 = BigInt::from_f32(-f).unwrap();
assert_eq!(neg_b1, neg_b2);
assert_eq!(neg_b1.to_f32().unwrap(), -f);
}
check(&BigInt::zero(), 0.0);
check(&BigInt::one(), 1.0);
check(&BigInt::from(u16::MAX), 2.0.powi(16) - 1.0);
check(&BigInt::from(1u64 << 32), 2.0.powi(32));
check(&BigInt::from_slice(Plus, &[0, 0, 1]), 2.0.powi(64));
check(&((BigInt::one() << 100) + (BigInt::one() << 123)), 2.0.powi(100) + 2.0.powi(123));
check(&(BigInt::one() << 127), 2.0.powi(127));
check(&(BigInt::from((1u64 << 24) - 1) << (128 - 24)), f32::MAX);
// keeping all 24 digits with the bits at different offsets to the BigDigits
let x: u32 = 0b00000000101111011111011011011101;
let mut f = x as f32;
let mut b = BigInt::from(x);
for _ in 0..64 {
check(&b, f);
f *= 2.0;
b = b << 1;
}
// this number when rounded to f64 then f32 isn't the same as when rounded straight to f32
let mut n: i64 = 0b0000000000111111111111111111111111011111111111111111111111111111;
assert!((n as f64) as f32 != n as f32);
assert_eq!(BigInt::from(n).to_f32(), Some(n as f32));
n = -n;
assert!((n as f64) as f32 != n as f32);
assert_eq!(BigInt::from(n).to_f32(), Some(n as f32));
// test rounding up with the bits at different offsets to the BigDigits
let mut f = ((1u64 << 25) - 1) as f32;
let mut b = BigInt::from(1u64 << 25);
for _ in 0..64 {
assert_eq!(b.to_f32(), Some(f));
f *= 2.0;
b = b << 1;
}
// rounding
assert_eq!(BigInt::from_f32(-f32::consts::PI), Some(BigInt::from(-3i32)));
assert_eq!(BigInt::from_f32(-f32::consts::E), Some(BigInt::from(-2i32)));
assert_eq!(BigInt::from_f32(-0.99999), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(-0.5), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(-0.0), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(f32::MIN_POSITIVE / 2.0), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(f32::MIN_POSITIVE), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(0.5), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(0.99999), Some(BigInt::zero()));
assert_eq!(BigInt::from_f32(f32::consts::E), Some(BigInt::from(2u32)));
assert_eq!(BigInt::from_f32(f32::consts::PI), Some(BigInt::from(3u32)));
// special float values
assert_eq!(BigInt::from_f32(f32::NAN), None);
assert_eq!(BigInt::from_f32(f32::INFINITY), None);
assert_eq!(BigInt::from_f32(f32::NEG_INFINITY), None);
// largest BigInt that will round to a finite f32 value
let big_num = (BigInt::one() << 128) - BigInt::one() - (BigInt::one() << (128 - 25));
assert_eq!(big_num.to_f32(), Some(f32::MAX));
assert_eq!((&big_num + BigInt::one()).to_f32(), None);
assert_eq!((-&big_num).to_f32(), Some(f32::MIN));
assert_eq!(((-&big_num) - BigInt::one()).to_f32(), None);
assert_eq!(((BigInt::one() << 128) - BigInt::one()).to_f32(), None);
assert_eq!((BigInt::one() << 128).to_f32(), None);
assert_eq!((-((BigInt::one() << 128) - BigInt::one())).to_f32(), None);
assert_eq!((-(BigInt::one() << 128)).to_f32(), None);
}
#[test]
fn test_convert_f64() {
fn check(b1: &BigInt, f: f64) {
let b2 = BigInt::from_f64(f).unwrap();
assert_eq!(b1, &b2);
assert_eq!(b1.to_f64().unwrap(), f);
let neg_b1 = -b1;
let neg_b2 = BigInt::from_f64(-f).unwrap();
assert_eq!(neg_b1, neg_b2);
assert_eq!(neg_b1.to_f64().unwrap(), -f);
}
check(&BigInt::zero(), 0.0);
check(&BigInt::one(), 1.0);
check(&BigInt::from(u32::MAX), 2.0.powi(32) - 1.0);
check(&BigInt::from(1u64 << 32), 2.0.powi(32));
check(&BigInt::from_slice(Plus, &[0, 0, 1]), 2.0.powi(64));
check(&((BigInt::one() << 100) + (BigInt::one() << 152)), 2.0.powi(100) + 2.0.powi(152));
check(&(BigInt::one() << 1023), 2.0.powi(1023));
check(&(BigInt::from((1u64 << 53) - 1) << (1024 - 53)), f64::MAX);
// keeping all 53 digits with the bits at different offsets to the BigDigits
let x: u64 = 0b0000000000011110111110110111111101110111101111011111011011011101;
let mut f = x as f64;
let mut b = BigInt::from(x);
for _ in 0..128 {
check(&b, f);
f *= 2.0;
b = b << 1;
}
// test rounding up with the bits at different offsets to the BigDigits
let mut f = ((1u64 << 54) - 1) as f64;
let mut b = BigInt::from(1u64 << 54);
for _ in 0..128 {
assert_eq!(b.to_f64(), Some(f));
f *= 2.0;
b = b << 1;
}
// rounding
assert_eq!(BigInt::from_f64(-f64::consts::PI), Some(BigInt::from(-3i32)));
assert_eq!(BigInt::from_f64(-f64::consts::E), Some(BigInt::from(-2i32)));
assert_eq!(BigInt::from_f64(-0.99999), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(-0.5), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(-0.0), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(f64::MIN_POSITIVE / 2.0), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(f64::MIN_POSITIVE), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(0.5), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(0.99999), Some(BigInt::zero()));
assert_eq!(BigInt::from_f64(f64::consts::E), Some(BigInt::from(2u32)));
assert_eq!(BigInt::from_f64(f64::consts::PI), Some(BigInt::from(3u32)));
// special float values
assert_eq!(BigInt::from_f64(f64::NAN), None);
assert_eq!(BigInt::from_f64(f64::INFINITY), None);
assert_eq!(BigInt::from_f64(f64::NEG_INFINITY), None);
// largest BigInt that will round to a finite f64 value
let big_num = (BigInt::one() << 1024) - BigInt::one() - (BigInt::one() << (1024 - 54));
assert_eq!(big_num.to_f64(), Some(f64::MAX));
assert_eq!((&big_num + BigInt::one()).to_f64(), None);
assert_eq!((-&big_num).to_f64(), Some(f64::MIN));
assert_eq!(((-&big_num) - BigInt::one()).to_f64(), None);
assert_eq!(((BigInt::one() << 1024) - BigInt::one()).to_f64(), None);
assert_eq!((BigInt::one() << 1024).to_f64(), None);
assert_eq!((-((BigInt::one() << 1024) - BigInt::one())).to_f64(), None);
assert_eq!((-(BigInt::one() << 1024)).to_f64(), None);
}
#[test]
fn test_convert_to_biguint() {
fn check(n: BigInt, ans_1: BigUint) {