1 | //! Utilities for working with hex float formats. |
2 | |
3 | use super::{Round, Status, f32_from_bits, f64_from_bits}; |
4 | |
5 | /// Construct a 16-bit float from hex float representation (C-style) |
6 | #[cfg (f16_enabled)] |
7 | pub const fn hf16(s: &str) -> f16 { |
8 | match parse_hex_exact(s, 16, 10) { |
9 | Ok(bits) => f16::from_bits(bits as u16), |
10 | Err(HexFloatParseError(s)) => panic!("{}" , s), |
11 | } |
12 | } |
13 | |
14 | /// Construct a 32-bit float from hex float representation (C-style) |
15 | #[allow (unused)] |
16 | pub const fn hf32(s: &str) -> f32 { |
17 | match parse_hex_exact(s, bits:32, sig_bits:23) { |
18 | Ok(bits: u128) => f32_from_bits(bits as u32), |
19 | Err(HexFloatParseError(s: &'static str)) => panic!("{}" , s), |
20 | } |
21 | } |
22 | |
23 | /// Construct a 64-bit float from hex float representation (C-style) |
24 | pub const fn hf64(s: &str) -> f64 { |
25 | match parse_hex_exact(s, bits:64, sig_bits:52) { |
26 | Ok(bits: u128) => f64_from_bits(bits as u64), |
27 | Err(HexFloatParseError(s: &'static str)) => panic!("{}" , s), |
28 | } |
29 | } |
30 | |
31 | /// Construct a 128-bit float from hex float representation (C-style) |
32 | #[cfg (f128_enabled)] |
33 | pub const fn hf128(s: &str) -> f128 { |
34 | match parse_hex_exact(s, 128, 112) { |
35 | Ok(bits) => f128::from_bits(bits), |
36 | Err(HexFloatParseError(s)) => panic!("{}" , s), |
37 | } |
38 | } |
39 | #[derive (Copy, Clone, Debug)] |
40 | pub struct HexFloatParseError(&'static str); |
41 | |
42 | /// Parses any float to its bitwise representation, returning an error if it cannot be represented exactly |
43 | pub const fn parse_hex_exact( |
44 | s: &str, |
45 | bits: u32, |
46 | sig_bits: u32, |
47 | ) -> Result<u128, HexFloatParseError> { |
48 | match parse_any(s, bits, sig_bits, Round::Nearest) { |
49 | Err(e: HexFloatParseError) => Err(e), |
50 | Ok((bits: u128, Status::OK)) => Ok(bits), |
51 | Ok((_, status: Status)) if status.overflow() => Err(HexFloatParseError("the value is too huge" )), |
52 | Ok((_, status: Status)) if status.underflow() => Err(HexFloatParseError("the value is too tiny" )), |
53 | Ok((_, status: Status)) if status.inexact() => Err(HexFloatParseError("the value is too precise" )), |
54 | Ok(_) => unreachable!(), |
55 | } |
56 | } |
57 | |
58 | /// Parse any float from hex to its bitwise representation. |
59 | pub const fn parse_any( |
60 | s: &str, |
61 | bits: u32, |
62 | sig_bits: u32, |
63 | round: Round, |
64 | ) -> Result<(u128, Status), HexFloatParseError> { |
65 | let mut b = s.as_bytes(); |
66 | |
67 | if sig_bits > 119 || bits > 128 || bits < sig_bits + 3 || bits > sig_bits + 30 { |
68 | return Err(HexFloatParseError("unsupported target float configuration" )); |
69 | } |
70 | |
71 | let neg = matches!(b, [b'-' , ..]); |
72 | if let &[b'-' | b'+' , ref rest @ ..] = b { |
73 | b = rest; |
74 | } |
75 | |
76 | let sign_bit = 1 << (bits - 1); |
77 | let quiet_bit = 1 << (sig_bits - 1); |
78 | let nan = sign_bit - quiet_bit; |
79 | let inf = nan - quiet_bit; |
80 | |
81 | let (mut x, status) = match *b { |
82 | [b'i' | b'I' , b'n' | b'N' , b'f' | b'F' ] => (inf, Status::OK), |
83 | [b'n' | b'N' , b'a' | b'A' , b'n' | b'N' ] => (nan, Status::OK), |
84 | [b'0' , b'x' | b'X' , ref rest @ ..] => { |
85 | let round = match (neg, round) { |
86 | // parse("-x", Round::Positive) == -parse("x", Round::Negative) |
87 | (true, Round::Positive) => Round::Negative, |
88 | (true, Round::Negative) => Round::Positive, |
89 | // rounding toward nearest or zero are symmetric |
90 | (true, Round::Nearest | Round::Zero) | (false, _) => round, |
91 | }; |
92 | match parse_finite(rest, bits, sig_bits, round) { |
93 | Err(e) => return Err(e), |
94 | Ok(res) => res, |
95 | } |
96 | } |
97 | _ => return Err(HexFloatParseError("no hex indicator" )), |
98 | }; |
99 | |
100 | if neg { |
101 | x ^= sign_bit; |
102 | } |
103 | |
104 | Ok((x, status)) |
105 | } |
106 | |
107 | const fn parse_finite( |
108 | b: &[u8], |
109 | bits: u32, |
110 | sig_bits: u32, |
111 | rounding_mode: Round, |
112 | ) -> Result<(u128, Status), HexFloatParseError> { |
113 | let exp_bits: u32 = bits - sig_bits - 1; |
114 | let max_msb: i32 = (1 << (exp_bits - 1)) - 1; |
115 | // The exponent of one ULP in the subnormals |
116 | let min_lsb: i32 = 1 - max_msb - sig_bits as i32; |
117 | |
118 | let (mut sig, mut exp) = match parse_hex(b) { |
119 | Err(e) => return Err(e), |
120 | Ok(Parsed { sig: 0, .. }) => return Ok((0, Status::OK)), |
121 | Ok(Parsed { sig, exp }) => (sig, exp), |
122 | }; |
123 | |
124 | let mut round_bits = u128_ilog2(sig) as i32 - sig_bits as i32; |
125 | |
126 | // Round at least up to min_lsb |
127 | if exp < min_lsb - round_bits { |
128 | round_bits = min_lsb - exp; |
129 | } |
130 | |
131 | let mut status = Status::OK; |
132 | |
133 | exp += round_bits; |
134 | |
135 | if round_bits > 0 { |
136 | // first, prepare for rounding exactly two bits |
137 | if round_bits == 1 { |
138 | sig <<= 1; |
139 | } else if round_bits > 2 { |
140 | sig = shr_odd_rounding(sig, (round_bits - 2) as u32); |
141 | } |
142 | |
143 | if sig & 0b11 != 0 { |
144 | status = Status::INEXACT; |
145 | } |
146 | |
147 | sig = shr2_round(sig, rounding_mode); |
148 | } else if round_bits < 0 { |
149 | sig <<= -round_bits; |
150 | } |
151 | |
152 | // The parsed value is X = sig * 2^exp |
153 | // Expressed as a multiple U of the smallest subnormal value: |
154 | // X = U * 2^min_lsb, so U = sig * 2^(exp-min_lsb) |
155 | let uexp = (exp - min_lsb) as u128; |
156 | let uexp = uexp << sig_bits; |
157 | |
158 | // Note that it is possible for the exponent bits to equal 2 here |
159 | // if the value rounded up, but that means the mantissa is all zeroes |
160 | // so the value is still correct |
161 | debug_assert!(sig <= 2 << sig_bits); |
162 | |
163 | let inf = ((1 << exp_bits) - 1) << sig_bits; |
164 | |
165 | let bits = match sig.checked_add(uexp) { |
166 | Some(bits) if bits < inf => { |
167 | // inexact subnormal or zero? |
168 | if status.inexact() && bits < (1 << sig_bits) { |
169 | status = status.with(Status::UNDERFLOW); |
170 | } |
171 | bits |
172 | } |
173 | _ => { |
174 | // overflow to infinity |
175 | status = status.with(Status::OVERFLOW).with(Status::INEXACT); |
176 | match rounding_mode { |
177 | Round::Positive | Round::Nearest => inf, |
178 | Round::Negative | Round::Zero => inf - 1, |
179 | } |
180 | } |
181 | }; |
182 | Ok((bits, status)) |
183 | } |
184 | |
185 | /// Shift right, rounding all inexact divisions to the nearest odd number |
186 | /// E.g. (0 >> 4) -> 0, (1..=31 >> 4) -> 1, (32 >> 4) -> 2, ... |
187 | /// |
188 | /// Useful for reducing a number before rounding the last two bits, since |
189 | /// the result of the final rounding is preserved for all rounding modes. |
190 | const fn shr_odd_rounding(x: u128, k: u32) -> u128 { |
191 | if k < 128 { |
192 | let inexact: bool = x.trailing_zeros() < k; |
193 | (x >> k) | (inexact as u128) |
194 | } else { |
195 | (x != 0) as u128 |
196 | } |
197 | } |
198 | |
199 | /// Divide by 4, rounding with the given mode |
200 | const fn shr2_round(mut x: u128, round: Round) -> u128 { |
201 | let t: u32 = (x as u32) & 0b111; |
202 | x >>= 2; |
203 | match round { |
204 | // Look-up-table on the last three bits for when to round up |
205 | Round::Nearest => x + ((0b11001000_u8 >> t) & 1) as u128, |
206 | |
207 | Round::Negative => x, |
208 | Round::Zero => x, |
209 | Round::Positive => x + (t & 0b11 != 0) as u128, |
210 | } |
211 | } |
212 | |
213 | /// A parsed finite and unsigned floating point number. |
214 | struct Parsed { |
215 | /// Absolute value sig * 2^exp |
216 | sig: u128, |
217 | exp: i32, |
218 | } |
219 | |
220 | /// Parse a hexadecimal float x |
221 | const fn parse_hex(mut b: &[u8]) -> Result<Parsed, HexFloatParseError> { |
222 | let mut sig: u128 = 0; |
223 | let mut exp: i32 = 0; |
224 | |
225 | let mut seen_point = false; |
226 | let mut some_digits = false; |
227 | let mut inexact = false; |
228 | |
229 | while let &[c, ref rest @ ..] = b { |
230 | b = rest; |
231 | |
232 | match c { |
233 | b'.' => { |
234 | if seen_point { |
235 | return Err(HexFloatParseError( |
236 | "unexpected '.' parsing fractional digits" , |
237 | )); |
238 | } |
239 | seen_point = true; |
240 | continue; |
241 | } |
242 | b'p' | b'P' => break, |
243 | c => { |
244 | let digit = match hex_digit(c) { |
245 | Some(d) => d, |
246 | None => return Err(HexFloatParseError("expected hexadecimal digit" )), |
247 | }; |
248 | some_digits = true; |
249 | |
250 | if (sig >> 124) == 0 { |
251 | sig <<= 4; |
252 | sig |= digit as u128; |
253 | } else { |
254 | // FIXME: it is technically possible for exp to overflow if parsing a string with >500M digits |
255 | exp += 4; |
256 | inexact |= digit != 0; |
257 | } |
258 | // Up until the fractional point, the value grows |
259 | // with more digits, but after it the exponent is |
260 | // compensated to match. |
261 | if seen_point { |
262 | exp -= 4; |
263 | } |
264 | } |
265 | } |
266 | } |
267 | // If we've set inexact, the exact value has more than 125 |
268 | // significant bits, and lies somewhere between sig and sig + 1. |
269 | // Because we'll round off at least two of the trailing bits, |
270 | // setting the last bit gives correct rounding for inexact values. |
271 | sig |= inexact as u128; |
272 | |
273 | if !some_digits { |
274 | return Err(HexFloatParseError("at least one digit is required" )); |
275 | }; |
276 | |
277 | some_digits = false; |
278 | |
279 | let negate_exp = matches!(b, [b'-' , ..]); |
280 | if let &[b'-' | b'+' , ref rest @ ..] = b { |
281 | b = rest; |
282 | } |
283 | |
284 | let mut pexp: u32 = 0; |
285 | while let &[c, ref rest @ ..] = b { |
286 | b = rest; |
287 | let digit = match dec_digit(c) { |
288 | Some(d) => d, |
289 | None => return Err(HexFloatParseError("expected decimal digit" )), |
290 | }; |
291 | some_digits = true; |
292 | pexp = pexp.saturating_mul(10); |
293 | pexp += digit as u32; |
294 | } |
295 | |
296 | if !some_digits { |
297 | return Err(HexFloatParseError( |
298 | "at least one exponent digit is required" , |
299 | )); |
300 | }; |
301 | |
302 | { |
303 | let e; |
304 | if negate_exp { |
305 | e = (exp as i64) - (pexp as i64); |
306 | } else { |
307 | e = (exp as i64) + (pexp as i64); |
308 | }; |
309 | |
310 | exp = if e < i32::MIN as i64 { |
311 | i32::MIN |
312 | } else if e > i32::MAX as i64 { |
313 | i32::MAX |
314 | } else { |
315 | e as i32 |
316 | }; |
317 | } |
318 | /* FIXME(msrv): once MSRV >= 1.66, replace the above workaround block with: |
319 | if negate_exp { |
320 | exp = exp.saturating_sub_unsigned(pexp); |
321 | } else { |
322 | exp = exp.saturating_add_unsigned(pexp); |
323 | }; |
324 | */ |
325 | |
326 | Ok(Parsed { sig, exp }) |
327 | } |
328 | |
329 | const fn dec_digit(c: u8) -> Option<u8> { |
330 | match c { |
331 | b'0' ..=b'9' => Some(c - b'0' ), |
332 | _ => None, |
333 | } |
334 | } |
335 | |
336 | const fn hex_digit(c: u8) -> Option<u8> { |
337 | match c { |
338 | b'0' ..=b'9' => Some(c - b'0' ), |
339 | b'a' ..=b'f' => Some(c - b'a' + 10), |
340 | b'A' ..=b'F' => Some(c - b'A' + 10), |
341 | _ => None, |
342 | } |
343 | } |
344 | |
345 | /* FIXME(msrv): vendor some things that are not const stable at our MSRV */ |
346 | |
347 | /// `u128::ilog2` |
348 | const fn u128_ilog2(v: u128) -> u32 { |
349 | assert!(v != 0); |
350 | u128::BITS - 1 - v.leading_zeros() |
351 | } |
352 | |
353 | #[cfg (any(test, feature = "unstable-public-internals" ))] |
354 | mod hex_fmt { |
355 | use core::fmt; |
356 | |
357 | use crate::support::Float; |
358 | |
359 | /// Format a floating point number as its IEEE hex (`%a`) representation. |
360 | pub struct Hexf<F>(pub F); |
361 | |
362 | // Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs |
363 | #[cfg (not(feature = "compiler-builtins" ))] |
364 | pub(super) fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
365 | if x.is_sign_negative() { |
366 | write!(f, "-" )?; |
367 | } |
368 | |
369 | if x.is_nan() { |
370 | return write!(f, "NaN" ); |
371 | } else if x.is_infinite() { |
372 | return write!(f, "inf" ); |
373 | } else if *x == F::ZERO { |
374 | return write!(f, "0x0p+0" ); |
375 | } |
376 | |
377 | let mut exponent = x.exp_unbiased(); |
378 | let sig = x.to_bits() & F::SIG_MASK; |
379 | |
380 | let bias = F::EXP_BIAS as i32; |
381 | // The mantissa MSB needs to be shifted up to the nearest nibble. |
382 | let mshift = (4 - (F::SIG_BITS % 4)) % 4; |
383 | let sig = sig << mshift; |
384 | // The width is rounded up to the nearest char (4 bits) |
385 | let mwidth = (F::SIG_BITS as usize + 3) / 4; |
386 | let leading = if exponent == -bias { |
387 | // subnormal number means we shift our output by 1 bit. |
388 | exponent += 1; |
389 | "0." |
390 | } else { |
391 | "1." |
392 | }; |
393 | |
394 | write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}" ) |
395 | } |
396 | |
397 | #[cfg (feature = "compiler-builtins" )] |
398 | pub(super) fn fmt_any_hex<F: Float>(_x: &F, _f: &mut fmt::Formatter<'_>) -> fmt::Result { |
399 | unimplemented!() |
400 | } |
401 | |
402 | impl<F: Float> fmt::LowerHex for Hexf<F> { |
403 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
404 | cfg_if! { |
405 | if #[cfg(feature = "compiler-builtins" )] { |
406 | let _ = f; |
407 | unimplemented!() |
408 | } else { |
409 | fmt_any_hex(&self.0, f) |
410 | } |
411 | } |
412 | } |
413 | } |
414 | |
415 | impl<F: Float> fmt::LowerHex for Hexf<(F, F)> { |
416 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
417 | cfg_if! { |
418 | if #[cfg(feature = "compiler-builtins" )] { |
419 | let _ = f; |
420 | unimplemented!() |
421 | } else { |
422 | write!(f, "({:x}, {:x})" , Hexf(self.0.0), Hexf(self.0.1)) |
423 | } |
424 | } |
425 | } |
426 | } |
427 | |
428 | impl<F: Float> fmt::LowerHex for Hexf<(F, i32)> { |
429 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
430 | cfg_if! { |
431 | if #[cfg(feature = "compiler-builtins" )] { |
432 | let _ = f; |
433 | unimplemented!() |
434 | } else { |
435 | write!(f, "({:x}, {:x})" , Hexf(self.0.0), Hexf(self.0.1)) |
436 | } |
437 | } |
438 | } |
439 | } |
440 | |
441 | impl fmt::LowerHex for Hexf<i32> { |
442 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
443 | cfg_if! { |
444 | if #[cfg(feature = "compiler-builtins" )] { |
445 | let _ = f; |
446 | unimplemented!() |
447 | } else { |
448 | fmt::LowerHex::fmt(&self.0, f) |
449 | } |
450 | } |
451 | } |
452 | } |
453 | |
454 | impl<T> fmt::Debug for Hexf<T> |
455 | where |
456 | Hexf<T>: fmt::LowerHex, |
457 | { |
458 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
459 | cfg_if! { |
460 | if #[cfg(feature = "compiler-builtins" )] { |
461 | let _ = f; |
462 | unimplemented!() |
463 | } else { |
464 | fmt::LowerHex::fmt(self, f) |
465 | } |
466 | } |
467 | } |
468 | } |
469 | |
470 | impl<T> fmt::Display for Hexf<T> |
471 | where |
472 | Hexf<T>: fmt::LowerHex, |
473 | { |
474 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
475 | cfg_if! { |
476 | if #[cfg(feature = "compiler-builtins" )] { |
477 | let _ = f; |
478 | unimplemented!() |
479 | } else { |
480 | fmt::LowerHex::fmt(self, f) |
481 | } |
482 | } |
483 | } |
484 | } |
485 | } |
486 | |
487 | #[cfg (any(test, feature = "unstable-public-internals" ))] |
488 | pub use hex_fmt::*; |
489 | |
490 | #[cfg (test)] |
491 | mod parse_tests { |
492 | extern crate std; |
493 | use std::{format, println}; |
494 | |
495 | use super::*; |
496 | |
497 | #[cfg (f16_enabled)] |
498 | fn rounding_properties(s: &str) -> Result<(), HexFloatParseError> { |
499 | let (xd, s0) = parse_any(s, 16, 10, Round::Negative)?; |
500 | let (xu, s1) = parse_any(s, 16, 10, Round::Positive)?; |
501 | let (xz, s2) = parse_any(s, 16, 10, Round::Zero)?; |
502 | let (xn, s3) = parse_any(s, 16, 10, Round::Nearest)?; |
503 | |
504 | // FIXME: A value between the least normal and largest subnormal |
505 | // could have underflow status depend on rounding mode. |
506 | |
507 | if let Status::OK = s0 { |
508 | // an exact result is the same for all rounding modes |
509 | assert_eq!(s0, s1); |
510 | assert_eq!(s0, s2); |
511 | assert_eq!(s0, s3); |
512 | |
513 | assert_eq!(xd, xu); |
514 | assert_eq!(xd, xz); |
515 | assert_eq!(xd, xn); |
516 | } else { |
517 | assert!([s0, s1, s2, s3].into_iter().all(Status::inexact)); |
518 | |
519 | let xd = f16::from_bits(xd as u16); |
520 | let xu = f16::from_bits(xu as u16); |
521 | let xz = f16::from_bits(xz as u16); |
522 | let xn = f16::from_bits(xn as u16); |
523 | |
524 | assert_biteq!(xd.next_up(), xu, "s={s}, xd={xd:?}, xu={xu:?}" ); |
525 | |
526 | let signs = [xd, xu, xz, xn].map(f16::is_sign_negative); |
527 | |
528 | if signs == [true; 4] { |
529 | assert_biteq!(xz, xu); |
530 | } else { |
531 | assert_eq!(signs, [false; 4]); |
532 | assert_biteq!(xz, xd); |
533 | } |
534 | |
535 | if xn.to_bits() != xd.to_bits() { |
536 | assert_biteq!(xn, xu); |
537 | } |
538 | } |
539 | Ok(()) |
540 | } |
541 | #[test ] |
542 | #[cfg (f16_enabled)] |
543 | fn test_rounding() { |
544 | let n = 1_i32 << 14; |
545 | for i in -n..n { |
546 | let u = i.rotate_right(11) as u32; |
547 | let s = format!("{}" , Hexf(f32::from_bits(u))); |
548 | assert!(rounding_properties(&s).is_ok()); |
549 | } |
550 | } |
551 | |
552 | #[test ] |
553 | fn test_parse_any() { |
554 | for k in -149..=127 { |
555 | let s = format!("0x1p{k}" ); |
556 | let x = hf32(&s); |
557 | let y = if k < 0 { |
558 | 0.5f32.powi(-k) |
559 | } else { |
560 | 2.0f32.powi(k) |
561 | }; |
562 | assert_eq!(x, y); |
563 | } |
564 | |
565 | let mut s = *b"0x.0000000p-121" ; |
566 | for e in 0..40 { |
567 | for k in 0..(1 << 15) { |
568 | let expected = f32::from_bits(k) * 2.0f32.powi(e); |
569 | let x = hf32(std::str::from_utf8(&s).unwrap()); |
570 | assert_eq!( |
571 | x.to_bits(), |
572 | expected.to_bits(), |
573 | "\ |
574 | e={e} \n\ |
575 | k={k} \n\ |
576 | x={x} \n\ |
577 | expected={expected} \n\ |
578 | s={} \n\ |
579 | f32::from_bits(k)={} \n\ |
580 | 2.0f32.powi(e)={}\ |
581 | " , |
582 | std::str::from_utf8(&s).unwrap(), |
583 | f32::from_bits(k), |
584 | 2.0f32.powi(e), |
585 | ); |
586 | for i in (3..10).rev() { |
587 | if s[i] == b'f' { |
588 | s[i] = b'0' ; |
589 | } else if s[i] == b'9' { |
590 | s[i] = b'a' ; |
591 | break; |
592 | } else { |
593 | s[i] += 1; |
594 | break; |
595 | } |
596 | } |
597 | } |
598 | for i in (12..15).rev() { |
599 | if s[i] == b'0' { |
600 | s[i] = b'9' ; |
601 | } else { |
602 | s[i] -= 1; |
603 | break; |
604 | } |
605 | } |
606 | for i in (3..10).rev() { |
607 | s[i] = b'0' ; |
608 | } |
609 | } |
610 | } |
611 | |
612 | // FIXME: this test is causing failures that are likely UB on various platforms |
613 | #[cfg (all(target_arch = "x86_64" , target_os = "linux" ))] |
614 | #[test ] |
615 | #[cfg (f128_enabled)] |
616 | fn rounding() { |
617 | let pi = std::f128::consts::PI; |
618 | let s = format!("{}" , Hexf(pi)); |
619 | |
620 | for k in 0..=111 { |
621 | let (bits, status) = parse_any(&s, 128 - k, 112 - k, Round::Nearest).unwrap(); |
622 | let scale = (1u128 << (112 - k - 1)) as f128; |
623 | let expected = (pi * scale).round_ties_even() / scale; |
624 | assert_eq!(bits << k, expected.to_bits(), "k = {k}, s = {s}" ); |
625 | assert_eq!(expected != pi, status.inexact()); |
626 | } |
627 | } |
628 | #[test ] |
629 | fn rounding_extreme_underflow() { |
630 | for k in 1..1000 { |
631 | let s = format!("0x1p{}" , -149 - k); |
632 | let Ok((bits, status)) = parse_any(&s, 32, 23, Round::Nearest) else { |
633 | unreachable!() |
634 | }; |
635 | assert_eq!(bits, 0, "{s} should round to zero, got bits={bits}" ); |
636 | assert!( |
637 | status.underflow(), |
638 | "should indicate underflow when parsing {s}" |
639 | ); |
640 | assert!(status.inexact(), "should indicate inexact when parsing {s}" ); |
641 | } |
642 | } |
643 | #[test ] |
644 | fn long_tail() { |
645 | for k in 1..1000 { |
646 | let s = format!("0x1.{}p0" , "0" .repeat(k)); |
647 | let Ok(bits) = parse_hex_exact(&s, 32, 23) else { |
648 | panic!("parsing {s} failed" ) |
649 | }; |
650 | assert_eq!(f32::from_bits(bits as u32), 1.0); |
651 | |
652 | let s = format!("0x1.{}1p0" , "0" .repeat(k)); |
653 | let Ok((bits, status)) = parse_any(&s, 32, 23, Round::Nearest) else { |
654 | unreachable!() |
655 | }; |
656 | if status.inexact() { |
657 | assert!(1.0 == f32::from_bits(bits as u32)); |
658 | } else { |
659 | assert!(1.0 < f32::from_bits(bits as u32)); |
660 | } |
661 | } |
662 | } |
663 | // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to |
664 | // hide them from the AST. |
665 | #[cfg (f16_enabled)] |
666 | macro_rules! f16_tests { |
667 | () => { |
668 | #[test] |
669 | fn test_f16() { |
670 | let checks = [ |
671 | ("0x.1234p+16" , (0x1234 as f16).to_bits()), |
672 | ("0x1.234p+12" , (0x1234 as f16).to_bits()), |
673 | ("0x12.34p+8" , (0x1234 as f16).to_bits()), |
674 | ("0x123.4p+4" , (0x1234 as f16).to_bits()), |
675 | ("0x1234p+0" , (0x1234 as f16).to_bits()), |
676 | ("0x1234.p+0" , (0x1234 as f16).to_bits()), |
677 | ("0x1234.0p+0" , (0x1234 as f16).to_bits()), |
678 | ("0x1.ffcp+15" , f16::MAX.to_bits()), |
679 | ("0x1.0p+1" , 2.0f16.to_bits()), |
680 | ("0x1.0p+0" , 1.0f16.to_bits()), |
681 | ("0x1.ffp+8" , 0x5ffc), |
682 | ("+0x1.ffp+8" , 0x5ffc), |
683 | ("0x1p+0" , 0x3c00), |
684 | ("0x1.998p-4" , 0x2e66), |
685 | ("0x1.9p+6" , 0x5640), |
686 | ("0x0.0p0" , 0.0f16.to_bits()), |
687 | ("-0x0.0p0" , (-0.0f16).to_bits()), |
688 | ("0x1.0p0" , 1.0f16.to_bits()), |
689 | ("0x1.998p-4" , (0.1f16).to_bits()), |
690 | ("-0x1.998p-4" , (-0.1f16).to_bits()), |
691 | ("0x0.123p-12" , 0x0123), |
692 | ("0x1p-24" , 0x0001), |
693 | ("nan" , f16::NAN.to_bits()), |
694 | ("-nan" , (-f16::NAN).to_bits()), |
695 | ("inf" , f16::INFINITY.to_bits()), |
696 | ("-inf" , f16::NEG_INFINITY.to_bits()), |
697 | ]; |
698 | for (s, exp) in checks { |
699 | println!("parsing {s}" ); |
700 | assert!(rounding_properties(s).is_ok()); |
701 | let act = hf16(s).to_bits(); |
702 | assert_eq!( |
703 | act, exp, |
704 | "parsing {s}: {act:#06x} != {exp:#06x} \nact: {act:#018b} \nexp: {exp:#018b}" |
705 | ); |
706 | } |
707 | } |
708 | |
709 | #[test] |
710 | fn test_macros_f16() { |
711 | assert_eq!(hf16!("0x1.ffp+8" ).to_bits(), 0x5ffc_u16); |
712 | } |
713 | }; |
714 | } |
715 | |
716 | #[cfg (f16_enabled)] |
717 | f16_tests!(); |
718 | |
719 | #[test ] |
720 | fn test_f32() { |
721 | let checks = [ |
722 | ("0x.1234p+16" , (0x1234 as f32).to_bits()), |
723 | ("0x1.234p+12" , (0x1234 as f32).to_bits()), |
724 | ("0x12.34p+8" , (0x1234 as f32).to_bits()), |
725 | ("0x123.4p+4" , (0x1234 as f32).to_bits()), |
726 | ("0x1234p+0" , (0x1234 as f32).to_bits()), |
727 | ("0x1234.p+0" , (0x1234 as f32).to_bits()), |
728 | ("0x1234.0p+0" , (0x1234 as f32).to_bits()), |
729 | ("0x1.fffffep+127" , f32::MAX.to_bits()), |
730 | ("0x1.0p+1" , 2.0f32.to_bits()), |
731 | ("0x1.0p+0" , 1.0f32.to_bits()), |
732 | ("0x1.ffep+8" , 0x43fff000), |
733 | ("+0x1.ffep+8" , 0x43fff000), |
734 | ("0x1p+0" , 0x3f800000), |
735 | ("0x1.99999ap-4" , 0x3dcccccd), |
736 | ("0x1.9p+6" , 0x42c80000), |
737 | ("0x1.2d5ed2p+20" , 0x4996af69), |
738 | ("-0x1.348eb8p+10" , 0xc49a475c), |
739 | ("-0x1.33dcfep-33" , 0xaf19ee7f), |
740 | ("0x0.0p0" , 0.0f32.to_bits()), |
741 | ("-0x0.0p0" , (-0.0f32).to_bits()), |
742 | ("0x1.0p0" , 1.0f32.to_bits()), |
743 | ("0x1.99999ap-4" , (0.1f32).to_bits()), |
744 | ("-0x1.99999ap-4" , (-0.1f32).to_bits()), |
745 | ("0x1.111114p-127" , 0x00444445), |
746 | ("0x1.23456p-130" , 0x00091a2b), |
747 | ("0x1p-149" , 0x00000001), |
748 | ("nan" , f32::NAN.to_bits()), |
749 | ("-nan" , (-f32::NAN).to_bits()), |
750 | ("inf" , f32::INFINITY.to_bits()), |
751 | ("-inf" , f32::NEG_INFINITY.to_bits()), |
752 | ]; |
753 | for (s, exp) in checks { |
754 | println!("parsing {s}" ); |
755 | let act = hf32(s).to_bits(); |
756 | assert_eq!( |
757 | act, exp, |
758 | "parsing {s}: {act:#010x} != {exp:#010x} \nact: {act:#034b} \nexp: {exp:#034b}" |
759 | ); |
760 | } |
761 | } |
762 | |
763 | #[test ] |
764 | fn test_f64() { |
765 | let checks = [ |
766 | ("0x.1234p+16" , (0x1234 as f64).to_bits()), |
767 | ("0x1.234p+12" , (0x1234 as f64).to_bits()), |
768 | ("0x12.34p+8" , (0x1234 as f64).to_bits()), |
769 | ("0x123.4p+4" , (0x1234 as f64).to_bits()), |
770 | ("0x1234p+0" , (0x1234 as f64).to_bits()), |
771 | ("0x1234.p+0" , (0x1234 as f64).to_bits()), |
772 | ("0x1234.0p+0" , (0x1234 as f64).to_bits()), |
773 | ("0x1.ffep+8" , 0x407ffe0000000000), |
774 | ("0x1p+0" , 0x3ff0000000000000), |
775 | ("0x1.999999999999ap-4" , 0x3fb999999999999a), |
776 | ("0x1.9p+6" , 0x4059000000000000), |
777 | ("0x1.2d5ed1fe1da7bp+20" , 0x4132d5ed1fe1da7b), |
778 | ("-0x1.348eb851eb852p+10" , 0xc09348eb851eb852), |
779 | ("-0x1.33dcfe54a3803p-33" , 0xbde33dcfe54a3803), |
780 | ("0x1.0p0" , 1.0f64.to_bits()), |
781 | ("0x0.0p0" , 0.0f64.to_bits()), |
782 | ("-0x0.0p0" , (-0.0f64).to_bits()), |
783 | ("0x1.999999999999ap-4" , 0.1f64.to_bits()), |
784 | ("0x1.999999999998ap-4" , (0.1f64 - f64::EPSILON).to_bits()), |
785 | ("-0x1.999999999999ap-4" , (-0.1f64).to_bits()), |
786 | ("-0x1.999999999998ap-4" , (-0.1f64 + f64::EPSILON).to_bits()), |
787 | ("0x0.8000000000001p-1022" , 0x0008000000000001), |
788 | ("0x0.123456789abcdp-1022" , 0x000123456789abcd), |
789 | ("0x0.0000000000002p-1022" , 0x0000000000000002), |
790 | ("nan" , f64::NAN.to_bits()), |
791 | ("-nan" , (-f64::NAN).to_bits()), |
792 | ("inf" , f64::INFINITY.to_bits()), |
793 | ("-inf" , f64::NEG_INFINITY.to_bits()), |
794 | ]; |
795 | for (s, exp) in checks { |
796 | println!("parsing {s}" ); |
797 | let act = hf64(s).to_bits(); |
798 | assert_eq!( |
799 | act, exp, |
800 | "parsing {s}: {act:#018x} != {exp:#018x} \nact: {act:#066b} \nexp: {exp:#066b}" |
801 | ); |
802 | } |
803 | } |
804 | |
805 | // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to |
806 | // hide them from the AST. |
807 | #[cfg (f128_enabled)] |
808 | macro_rules! f128_tests { |
809 | () => { |
810 | #[test] |
811 | fn test_f128() { |
812 | let checks = [ |
813 | ("0x.1234p+16" , (0x1234 as f128).to_bits()), |
814 | ("0x1.234p+12" , (0x1234 as f128).to_bits()), |
815 | ("0x12.34p+8" , (0x1234 as f128).to_bits()), |
816 | ("0x123.4p+4" , (0x1234 as f128).to_bits()), |
817 | ("0x1234p+0" , (0x1234 as f128).to_bits()), |
818 | ("0x1234.p+0" , (0x1234 as f128).to_bits()), |
819 | ("0x1234.0p+0" , (0x1234 as f128).to_bits()), |
820 | ("0x1.ffffffffffffffffffffffffffffp+16383" , f128::MAX.to_bits()), |
821 | ("0x1.0p+1" , 2.0f128.to_bits()), |
822 | ("0x1.0p+0" , 1.0f128.to_bits()), |
823 | ("0x1.ffep+8" , 0x4007ffe0000000000000000000000000), |
824 | ("+0x1.ffep+8" , 0x4007ffe0000000000000000000000000), |
825 | ("0x1p+0" , 0x3fff0000000000000000000000000000), |
826 | ("0x1.999999999999999999999999999ap-4" , 0x3ffb999999999999999999999999999a), |
827 | ("0x1.9p+6" , 0x40059000000000000000000000000000), |
828 | ("0x0.0p0" , 0.0f128.to_bits()), |
829 | ("-0x0.0p0" , (-0.0f128).to_bits()), |
830 | ("0x1.0p0" , 1.0f128.to_bits()), |
831 | ("0x1.999999999999999999999999999ap-4" , (0.1f128).to_bits()), |
832 | ("-0x1.999999999999999999999999999ap-4" , (-0.1f128).to_bits()), |
833 | ("0x0.abcdef0123456789abcdef012345p-16382" , 0x0000abcdef0123456789abcdef012345), |
834 | ("0x1p-16494" , 0x00000000000000000000000000000001), |
835 | ("nan" , f128::NAN.to_bits()), |
836 | ("-nan" , (-f128::NAN).to_bits()), |
837 | ("inf" , f128::INFINITY.to_bits()), |
838 | ("-inf" , f128::NEG_INFINITY.to_bits()), |
839 | ]; |
840 | for (s, exp) in checks { |
841 | println!("parsing {s}" ); |
842 | let act = hf128(s).to_bits(); |
843 | assert_eq!( |
844 | act, exp, |
845 | "parsing {s}: {act:#034x} != {exp:#034x} \nact: {act:#0130b} \nexp: {exp:#0130b}" |
846 | ); |
847 | } |
848 | } |
849 | |
850 | #[test] |
851 | fn test_macros_f128() { |
852 | assert_eq!(hf128!("0x1.ffep+8" ).to_bits(), 0x4007ffe0000000000000000000000000_u128); |
853 | } |
854 | } |
855 | } |
856 | |
857 | #[cfg (f128_enabled)] |
858 | f128_tests!(); |
859 | |
860 | #[test ] |
861 | fn test_macros() { |
862 | #[cfg (f16_enabled)] |
863 | assert_eq!(hf16!("0x1.ffp+8" ).to_bits(), 0x5ffc_u16); |
864 | assert_eq!(hf32!("0x1.ffep+8" ).to_bits(), 0x43fff000_u32); |
865 | assert_eq!(hf64!("0x1.ffep+8" ).to_bits(), 0x407ffe0000000000_u64); |
866 | #[cfg (f128_enabled)] |
867 | assert_eq!( |
868 | hf128!("0x1.ffep+8" ).to_bits(), |
869 | 0x4007ffe0000000000000000000000000_u128 |
870 | ); |
871 | } |
872 | } |
873 | |
874 | #[cfg (test)] |
875 | // FIXME(ppc): something with `should_panic` tests cause a SIGILL with ppc64le |
876 | #[cfg (not(all(target_arch = "powerpc64" , target_endian = "little" )))] |
877 | mod tests_panicking { |
878 | extern crate std; |
879 | use super::*; |
880 | |
881 | // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to |
882 | // hide them from the AST. |
883 | #[cfg (f16_enabled)] |
884 | macro_rules! f16_tests { |
885 | () => { |
886 | #[test] |
887 | fn test_f16_almost_extra_precision() { |
888 | // Exact maximum precision allowed |
889 | hf16("0x1.ffcp+0" ); |
890 | } |
891 | |
892 | #[test] |
893 | #[should_panic(expected = "the value is too precise" )] |
894 | fn test_f16_extra_precision() { |
895 | // One bit more than the above. |
896 | hf16("0x1.ffdp+0" ); |
897 | } |
898 | |
899 | #[test] |
900 | #[should_panic(expected = "the value is too huge" )] |
901 | fn test_f16_overflow() { |
902 | // One bit more than the above. |
903 | hf16("0x1p+16" ); |
904 | } |
905 | |
906 | #[test] |
907 | fn test_f16_tiniest() { |
908 | let x = hf16("0x1.p-24" ); |
909 | let y = hf16("0x0.001p-12" ); |
910 | let z = hf16("0x0.8p-23" ); |
911 | assert_eq!(x, y); |
912 | assert_eq!(x, z); |
913 | } |
914 | |
915 | #[test] |
916 | #[should_panic(expected = "the value is too tiny" )] |
917 | fn test_f16_too_tiny() { |
918 | hf16("0x1.p-25" ); |
919 | } |
920 | |
921 | #[test] |
922 | #[should_panic(expected = "the value is too tiny" )] |
923 | fn test_f16_also_too_tiny() { |
924 | hf16("0x0.8p-24" ); |
925 | } |
926 | |
927 | #[test] |
928 | #[should_panic(expected = "the value is too tiny" )] |
929 | fn test_f16_again_too_tiny() { |
930 | hf16("0x0.001p-13" ); |
931 | } |
932 | }; |
933 | } |
934 | |
935 | #[cfg (f16_enabled)] |
936 | f16_tests!(); |
937 | |
938 | #[test ] |
939 | fn test_f32_almost_extra_precision() { |
940 | // Exact maximum precision allowed |
941 | hf32("0x1.abcdeep+0" ); |
942 | } |
943 | |
944 | #[test ] |
945 | #[should_panic ] |
946 | fn test_f32_extra_precision2() { |
947 | // One bit more than the above. |
948 | hf32("0x1.ffffffp+127" ); |
949 | } |
950 | |
951 | #[test ] |
952 | #[should_panic (expected = "the value is too huge" )] |
953 | fn test_f32_overflow() { |
954 | // One bit more than the above. |
955 | hf32("0x1p+128" ); |
956 | } |
957 | |
958 | #[test ] |
959 | #[should_panic (expected = "the value is too precise" )] |
960 | fn test_f32_extra_precision() { |
961 | // One bit more than the above. |
962 | hf32("0x1.abcdefp+0" ); |
963 | } |
964 | |
965 | #[test ] |
966 | fn test_f32_tiniest() { |
967 | let x = hf32("0x1.p-149" ); |
968 | let y = hf32("0x0.0000000000000001p-85" ); |
969 | let z = hf32("0x0.8p-148" ); |
970 | assert_eq!(x, y); |
971 | assert_eq!(x, z); |
972 | } |
973 | |
974 | #[test ] |
975 | #[should_panic (expected = "the value is too tiny" )] |
976 | fn test_f32_too_tiny() { |
977 | hf32("0x1.p-150" ); |
978 | } |
979 | |
980 | #[test ] |
981 | #[should_panic (expected = "the value is too tiny" )] |
982 | fn test_f32_also_too_tiny() { |
983 | hf32("0x0.8p-149" ); |
984 | } |
985 | |
986 | #[test ] |
987 | #[should_panic (expected = "the value is too tiny" )] |
988 | fn test_f32_again_too_tiny() { |
989 | hf32("0x0.0000000000000001p-86" ); |
990 | } |
991 | |
992 | #[test ] |
993 | fn test_f64_almost_extra_precision() { |
994 | // Exact maximum precision allowed |
995 | hf64("0x1.abcdabcdabcdfp+0" ); |
996 | } |
997 | |
998 | #[test ] |
999 | #[should_panic (expected = "the value is too precise" )] |
1000 | fn test_f64_extra_precision() { |
1001 | // One bit more than the above. |
1002 | hf64("0x1.abcdabcdabcdf8p+0" ); |
1003 | } |
1004 | |
1005 | // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to |
1006 | // hide them from the AST. |
1007 | #[cfg (f128_enabled)] |
1008 | macro_rules! f128_tests { |
1009 | () => { |
1010 | #[test] |
1011 | fn test_f128_almost_extra_precision() { |
1012 | // Exact maximum precision allowed |
1013 | hf128("0x1.ffffffffffffffffffffffffffffp+16383" ); |
1014 | } |
1015 | |
1016 | #[test] |
1017 | #[should_panic(expected = "the value is too precise" )] |
1018 | fn test_f128_extra_precision() { |
1019 | // Just below the maximum finite. |
1020 | hf128("0x1.fffffffffffffffffffffffffffe8p+16383" ); |
1021 | } |
1022 | #[test] |
1023 | #[should_panic(expected = "the value is too huge" )] |
1024 | fn test_f128_extra_precision_overflow() { |
1025 | // One bit more than the above. Should overflow. |
1026 | hf128("0x1.ffffffffffffffffffffffffffff8p+16383" ); |
1027 | } |
1028 | |
1029 | #[test] |
1030 | #[should_panic(expected = "the value is too huge" )] |
1031 | fn test_f128_overflow() { |
1032 | // One bit more than the above. |
1033 | hf128("0x1p+16384" ); |
1034 | } |
1035 | |
1036 | #[test] |
1037 | fn test_f128_tiniest() { |
1038 | let x = hf128("0x1.p-16494" ); |
1039 | let y = hf128("0x0.0000000000000001p-16430" ); |
1040 | let z = hf128("0x0.8p-16493" ); |
1041 | assert_eq!(x, y); |
1042 | assert_eq!(x, z); |
1043 | } |
1044 | |
1045 | #[test] |
1046 | #[should_panic(expected = "the value is too tiny" )] |
1047 | fn test_f128_too_tiny() { |
1048 | hf128("0x1.p-16495" ); |
1049 | } |
1050 | |
1051 | #[test] |
1052 | #[should_panic(expected = "the value is too tiny" )] |
1053 | fn test_f128_again_too_tiny() { |
1054 | hf128("0x0.0000000000000001p-16431" ); |
1055 | } |
1056 | |
1057 | #[test] |
1058 | #[should_panic(expected = "the value is too tiny" )] |
1059 | fn test_f128_also_too_tiny() { |
1060 | hf128("0x0.8p-16494" ); |
1061 | } |
1062 | }; |
1063 | } |
1064 | |
1065 | #[cfg (f128_enabled)] |
1066 | f128_tests!(); |
1067 | } |
1068 | |
1069 | #[cfg (test)] |
1070 | mod print_tests { |
1071 | extern crate std; |
1072 | use std::string::ToString; |
1073 | |
1074 | use super::*; |
1075 | use crate::support::Float; |
1076 | |
1077 | #[test ] |
1078 | #[cfg (f16_enabled)] |
1079 | fn test_f16() { |
1080 | use std::format; |
1081 | // Exhaustively check that `f16` roundtrips. |
1082 | for x in 0..=u16::MAX { |
1083 | let f = f16::from_bits(x); |
1084 | let s = format!("{}" , Hexf(f)); |
1085 | let from_s = hf16(&s); |
1086 | |
1087 | if f.is_nan() && from_s.is_nan() { |
1088 | continue; |
1089 | } |
1090 | |
1091 | assert_eq!( |
1092 | f.to_bits(), |
1093 | from_s.to_bits(), |
1094 | "{f:?} formatted as {s} but parsed as {from_s:?}" |
1095 | ); |
1096 | } |
1097 | } |
1098 | |
1099 | #[test ] |
1100 | #[cfg (f16_enabled)] |
1101 | fn test_f16_to_f32() { |
1102 | use std::format; |
1103 | // Exhaustively check that these are equivalent for all `f16`: |
1104 | // - `f16 -> f32` |
1105 | // - `f16 -> str -> f32` |
1106 | // - `f16 -> f32 -> str -> f32` |
1107 | // - `f16 -> f32 -> str -> f16 -> f32` |
1108 | for x in 0..=u16::MAX { |
1109 | let f16 = f16::from_bits(x); |
1110 | let s16 = format!("{}" , Hexf(f16)); |
1111 | let f32 = f16 as f32; |
1112 | let s32 = format!("{}" , Hexf(f32)); |
1113 | |
1114 | let a = hf32(&s16); |
1115 | let b = hf32(&s32); |
1116 | let c = hf16(&s32); |
1117 | |
1118 | if f32.is_nan() && a.is_nan() && b.is_nan() && c.is_nan() { |
1119 | continue; |
1120 | } |
1121 | |
1122 | assert_eq!( |
1123 | f32.to_bits(), |
1124 | a.to_bits(), |
1125 | "{f16:?} : f16 formatted as {s16} which parsed as {a:?} : f16" |
1126 | ); |
1127 | assert_eq!( |
1128 | f32.to_bits(), |
1129 | b.to_bits(), |
1130 | "{f32:?} : f32 formatted as {s32} which parsed as {b:?} : f32" |
1131 | ); |
1132 | assert_eq!( |
1133 | f32.to_bits(), |
1134 | (c as f32).to_bits(), |
1135 | "{f32:?} : f32 formatted as {s32} which parsed as {c:?} : f16" |
1136 | ); |
1137 | } |
1138 | } |
1139 | #[test ] |
1140 | fn spot_checks() { |
1141 | assert_eq!(Hexf(f32::MAX).to_string(), "0x1.fffffep+127" ); |
1142 | assert_eq!(Hexf(f64::MAX).to_string(), "0x1.fffffffffffffp+1023" ); |
1143 | |
1144 | assert_eq!(Hexf(f32::MIN).to_string(), "-0x1.fffffep+127" ); |
1145 | assert_eq!(Hexf(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023" ); |
1146 | |
1147 | assert_eq!(Hexf(f32::ZERO).to_string(), "0x0p+0" ); |
1148 | assert_eq!(Hexf(f64::ZERO).to_string(), "0x0p+0" ); |
1149 | |
1150 | assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0" ); |
1151 | assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0" ); |
1152 | |
1153 | assert_eq!(Hexf(f32::NAN).to_string(), "NaN" ); |
1154 | assert_eq!(Hexf(f64::NAN).to_string(), "NaN" ); |
1155 | |
1156 | assert_eq!(Hexf(f32::INFINITY).to_string(), "inf" ); |
1157 | assert_eq!(Hexf(f64::INFINITY).to_string(), "inf" ); |
1158 | |
1159 | assert_eq!(Hexf(f32::NEG_INFINITY).to_string(), "-inf" ); |
1160 | assert_eq!(Hexf(f64::NEG_INFINITY).to_string(), "-inf" ); |
1161 | |
1162 | #[cfg (f16_enabled)] |
1163 | { |
1164 | assert_eq!(Hexf(f16::MAX).to_string(), "0x1.ffcp+15" ); |
1165 | assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15" ); |
1166 | assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0" ); |
1167 | assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0" ); |
1168 | assert_eq!(Hexf(f16::NAN).to_string(), "NaN" ); |
1169 | assert_eq!(Hexf(f16::INFINITY).to_string(), "inf" ); |
1170 | assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf" ); |
1171 | } |
1172 | |
1173 | #[cfg (f128_enabled)] |
1174 | { |
1175 | assert_eq!( |
1176 | Hexf(f128::MAX).to_string(), |
1177 | "0x1.ffffffffffffffffffffffffffffp+16383" |
1178 | ); |
1179 | assert_eq!( |
1180 | Hexf(f128::MIN).to_string(), |
1181 | "-0x1.ffffffffffffffffffffffffffffp+16383" |
1182 | ); |
1183 | assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0" ); |
1184 | assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0" ); |
1185 | assert_eq!(Hexf(f128::NAN).to_string(), "NaN" ); |
1186 | assert_eq!(Hexf(f128::INFINITY).to_string(), "inf" ); |
1187 | assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf" ); |
1188 | } |
1189 | } |
1190 | } |
1191 | |