Skip to content

Commit 4fd1df7

Browse files
authored
Handle special values in f64 conversion (#55)
1 parent e7a99a5 commit 4fd1df7

File tree

1 file changed

+167
-94
lines changed

1 file changed

+167
-94
lines changed

src/float_ext.rs

Lines changed: 167 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,126 +7,166 @@ pub trait FloatExt {
77

88
impl FloatExt for Float {
99
fn display(&self) -> String {
10-
match () {
11-
_ if self.is_nan() => "nan".into(),
12-
_ if self.is_inf_pos() => "inf".into(),
13-
_ if self.is_inf_neg() => "-inf".into(),
14-
_ if self.is_zero() => "0".into(),
15-
_ => {
16-
let formatted = with_consts(|consts| {
17-
self.format(Radix::Dec, astro_float::RoundingMode::None, consts)
18-
})
19-
.unwrap();
20-
21-
if !formatted.contains('e') {
22-
return formatted;
23-
}
24-
25-
let (mantissa, exponent) = {
26-
let mut parts = formatted.split('e');
27-
(parts.next().unwrap(), parts.next().unwrap())
28-
};
29-
30-
let exponent = exponent.parse::<i32>().unwrap();
31-
32-
let (sign, mantissa) =
33-
mantissa.split_at(if mantissa.starts_with('-') { 1 } else { 0 });
34-
35-
let digits = mantissa.replace('.', "");
36-
37-
let length =
38-
mantissa.find('.').unwrap_or(mantissa.len()) as i32 + exponent;
39-
40-
let result = match length {
41-
length if length <= 0 => {
42-
format!("{}0.{}{}", sign, "0".repeat((-length) as usize), digits)
43-
}
44-
length if (length as usize) >= digits.len() => {
45-
format!(
46-
"{}{}{}",
47-
sign,
48-
digits,
49-
"0".repeat(length as usize - digits.len())
50-
)
51-
}
52-
_ => {
53-
let (left, right) = digits.split_at(length as usize);
54-
format!("{}{}.{}", sign, left, right)
55-
}
56-
};
57-
58-
if result.find('.').is_some() {
59-
return result
60-
.trim_end_matches('0')
61-
.trim_end_matches('.')
62-
.to_string();
63-
}
64-
65-
result
10+
if self.is_nan() {
11+
return "nan".into();
12+
}
13+
14+
if self.is_inf_pos() {
15+
return "inf".into();
16+
}
17+
18+
if self.is_inf_neg() {
19+
return "-inf".into();
20+
}
21+
22+
if self.is_zero() {
23+
return "0".into();
24+
}
25+
26+
let formatted = with_consts(|consts| {
27+
self.format(Radix::Dec, astro_float::RoundingMode::None, consts)
28+
})
29+
.expect("failed to format Float as decimal");
30+
31+
let Some((mantissa_with_sign, exponent_str)) = formatted.split_once('e')
32+
else {
33+
return formatted;
34+
};
35+
36+
let Ok(exponent) = exponent_str.parse::<i32>() else {
37+
return formatted;
38+
};
39+
40+
let (sign, mantissa) =
41+
if let Some(rest) = mantissa_with_sign.strip_prefix('-') {
42+
("-", rest)
43+
} else if let Some(rest) = mantissa_with_sign.strip_prefix('+') {
44+
("", rest)
45+
} else {
46+
("", mantissa_with_sign)
47+
};
48+
49+
let mut parts = mantissa.split('.');
50+
let int_part = parts.next().unwrap_or("");
51+
let frac_part = parts.next().unwrap_or("");
52+
53+
let mut digits = String::with_capacity(int_part.len() + frac_part.len());
54+
digits.push_str(int_part);
55+
digits.push_str(frac_part);
56+
57+
let length = int_part.len() as i32 + exponent;
58+
let digits_len = digits.len() as i32;
59+
60+
let mut result = if length <= 0 {
61+
let zeros = (-length) as usize;
62+
let mut out =
63+
String::with_capacity(sign.len() + 2 + zeros + digits.len());
64+
out.push_str(sign);
65+
out.push('0');
66+
out.push('.');
67+
out.extend(std::iter::repeat_n('0', zeros));
68+
out.push_str(&digits);
69+
out
70+
} else if length >= digits_len {
71+
let zeros = (length - digits_len) as usize;
72+
let mut out = String::with_capacity(sign.len() + digits.len() + zeros);
73+
out.push_str(sign);
74+
out.push_str(&digits);
75+
out.extend(std::iter::repeat_n('0', zeros));
76+
out
77+
} else {
78+
let split_at = length as usize;
79+
let (left, right) = digits.split_at(split_at);
80+
let mut out =
81+
String::with_capacity(sign.len() + left.len() + 1 + right.len());
82+
out.push_str(sign);
83+
out.push_str(left);
84+
out.push('.');
85+
out.push_str(right);
86+
out
87+
};
88+
89+
if result.contains('.') {
90+
while result.ends_with('0') {
91+
result.pop();
92+
}
93+
94+
if result.ends_with('.') {
95+
result.pop();
6696
}
6797
}
98+
99+
result
68100
}
69101

70102
fn to_f64(&self, rounding_mode: astro_float::RoundingMode) -> Option<f64> {
71-
let mut big_float = self.clone();
72-
73-
big_float.set_precision(64, rounding_mode).ok()?;
74-
75-
let sign = big_float.sign()?;
103+
if self.is_nan() {
104+
return None;
105+
}
76106

77-
let exponent = big_float.exponent()? as isize;
107+
if self.is_inf_pos() {
108+
return Some(f64::INFINITY);
109+
}
78110

79-
let mantissa_digits = big_float.mantissa_digits()?;
111+
if self.is_inf_neg() {
112+
return Some(f64::NEG_INFINITY);
113+
}
80114

81-
if mantissa_digits.is_empty() {
115+
if self.is_zero() {
82116
return Some(0.0);
83117
}
84118

85-
let mantissa = mantissa_digits[0];
119+
let mut big_float = self.clone();
120+
big_float.set_precision(64, rounding_mode).ok()?;
121+
122+
let sign = big_float.sign()?;
123+
let mut exponent = big_float.exponent()? as isize;
124+
let mantissa_digits = big_float.mantissa_digits()?;
125+
let mantissa = *mantissa_digits.first().unwrap_or(&0);
126+
127+
const F64_EXPONENT_BIAS: isize = 0x3ff;
128+
const F64_EXPONENT_MAX: isize = 0x7ff;
129+
const F64_SIGNIFICAND_BITS: usize = 52;
130+
const INTERNAL_SHIFT: usize = 12;
131+
const SIGN_MASK: u64 = 1u64 << 63;
86132

87133
if mantissa == 0 {
88-
return Some(0.0);
134+
return Some(if sign == Sign::Neg {
135+
f64::from_bits(SIGN_MASK)
136+
} else {
137+
0.0
138+
});
89139
}
90140

91-
let mut exponent = exponent + 0b1111111111;
141+
exponent += F64_EXPONENT_BIAS;
92142

93-
let mut ret = 0u64;
94-
95-
if exponent >= 0b11111111111 {
96-
Some(match sign {
143+
if exponent >= F64_EXPONENT_MAX {
144+
return Some(match sign {
97145
Sign::Pos => f64::INFINITY,
98146
Sign::Neg => f64::NEG_INFINITY,
99-
})
100-
} else if exponent <= 0 {
101-
let shift = -exponent;
147+
});
148+
}
102149

103-
if shift < 52 {
104-
ret |= mantissa >> (shift + 12);
150+
let sign_bit = if sign == Sign::Neg { SIGN_MASK } else { 0 };
105151

106-
if sign == Sign::Neg {
107-
ret |= 0x8000000000000000u64;
108-
}
152+
if exponent <= 0 {
153+
let shift = (-exponent) as usize;
109154

110-
Some(f64::from_bits(ret))
111-
} else {
112-
Some(0.0)
155+
if shift >= F64_SIGNIFICAND_BITS {
156+
return Some(f64::from_bits(sign_bit));
113157
}
114-
} else {
115-
let mantissa = mantissa << 1;
116158

117-
exponent -= 1;
159+
let fraction = mantissa >> (shift + INTERNAL_SHIFT);
118160

119-
if sign == Sign::Neg {
120-
ret |= 1;
121-
}
161+
return Some(f64::from_bits(sign_bit | fraction));
162+
}
122163

123-
ret <<= 11;
124-
ret |= exponent as u64;
125-
ret <<= 52;
126-
ret |= mantissa >> 12;
164+
let adjusted_mantissa = mantissa << 1;
165+
let adjusted_exponent = (exponent - 1) as u64;
166+
let exponent_bits = adjusted_exponent << F64_SIGNIFICAND_BITS;
167+
let fraction_bits = adjusted_mantissa >> INTERNAL_SHIFT;
127168

128-
Some(f64::from_bits(ret))
129-
}
169+
Some(f64::from_bits(sign_bit | exponent_bits | fraction_bits))
130170
}
131171
}
132172

@@ -210,4 +250,37 @@ mod tests {
210250
Some(-1.0)
211251
);
212252
}
253+
254+
#[test]
255+
fn convert_special_values_to_double_precision() {
256+
assert_eq!(
257+
Float::from(f64::INFINITY).to_f64(astro_float::RoundingMode::ToEven),
258+
Some(f64::INFINITY)
259+
);
260+
261+
assert_eq!(
262+
Float::from(f64::NEG_INFINITY).to_f64(astro_float::RoundingMode::ToEven),
263+
Some(f64::NEG_INFINITY)
264+
);
265+
266+
assert_eq!(
267+
Float::nan(None).to_f64(astro_float::RoundingMode::ToEven),
268+
None
269+
);
270+
}
271+
272+
#[test]
273+
fn convert_underflow_preserves_sign() {
274+
let tiny_negative = float_from_str("-1e-4000");
275+
276+
let result = tiny_negative.to_f64(astro_float::RoundingMode::ToEven);
277+
278+
assert!(result.is_some());
279+
280+
let value = result.unwrap();
281+
282+
assert!(value.is_sign_negative());
283+
284+
assert_eq!(value, -0.0);
285+
}
213286
}

0 commit comments

Comments
 (0)