1//! Utilities for working with hex float formats.
2
3use core::fmt;
4
5use super::{Float, f32_from_bits, f64_from_bits};
6
7/// Construct a 16-bit float from hex float representation (C-style)
8#[cfg(f16_enabled)]
9pub const fn hf16(s: &str) -> f16 {
10 f16::from_bits(parse_any(s, 16, 10) as u16)
11}
12
13/// Construct a 32-bit float from hex float representation (C-style)
14#[allow(unused)]
15pub const fn hf32(s: &str) -> f32 {
16 f32_from_bits(parse_any(s, bits:32, sig_bits:23) as u32)
17}
18
19/// Construct a 64-bit float from hex float representation (C-style)
20pub const fn hf64(s: &str) -> f64 {
21 f64_from_bits(parse_any(s, bits:64, sig_bits:52) as u64)
22}
23
24/// Construct a 128-bit float from hex float representation (C-style)
25#[cfg(f128_enabled)]
26pub const fn hf128(s: &str) -> f128 {
27 f128::from_bits(parse_any(s, 128, 112))
28}
29
30/// Parse any float from hex to its bitwise representation.
31///
32/// `nan_repr` is passed rather than constructed so the platform-specific NaN is returned.
33pub const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
34 let exp_bits: u32 = bits - sig_bits - 1;
35 let max_msb: i32 = (1 << (exp_bits - 1)) - 1;
36 // The exponent of one ULP in the subnormals
37 let min_lsb: i32 = 1 - max_msb - sig_bits as i32;
38
39 let exp_mask = ((1 << exp_bits) - 1) << sig_bits;
40
41 let (neg, mut sig, exp) = match parse_hex(s.as_bytes()) {
42 Parsed::Finite { neg, sig: 0, .. } => return (neg as u128) << (bits - 1),
43 Parsed::Finite { neg, sig, exp } => (neg, sig, exp),
44 Parsed::Infinite { neg } => return ((neg as u128) << (bits - 1)) | exp_mask,
45 Parsed::Nan { neg } => {
46 return ((neg as u128) << (bits - 1)) | exp_mask | (1 << (sig_bits - 1));
47 }
48 };
49
50 // exponents of the least and most significant bits in the value
51 let lsb = sig.trailing_zeros() as i32;
52 let msb = u128_ilog2(sig) as i32;
53 let sig_bits = sig_bits as i32;
54
55 assert!(msb - lsb <= sig_bits, "the value is too precise");
56 assert!(msb + exp <= max_msb, "the value is too huge");
57 assert!(lsb + exp >= min_lsb, "the value is too tiny");
58
59 // The parsed value is X = sig * 2^exp
60 // Expressed as a multiple U of the smallest subnormal value:
61 // X = U * 2^min_lsb, so U = sig * 2^(exp-min_lsb)
62 let mut uexp = exp - min_lsb;
63
64 let shift = if uexp + msb >= sig_bits {
65 // normal, shift msb to position sig_bits
66 sig_bits - msb
67 } else {
68 // subnormal, shift so that uexp becomes 0
69 uexp
70 };
71
72 if shift >= 0 {
73 sig <<= shift;
74 } else {
75 sig >>= -shift;
76 }
77 uexp -= shift;
78
79 // the most significant bit is like having 1 in the exponent bits
80 // add any leftover exponent to that
81 assert!(uexp >= 0 && uexp < (1 << exp_bits) - 2);
82 sig += (uexp as u128) << sig_bits;
83
84 // finally, set the sign bit if necessary
85 sig | ((neg as u128) << (bits - 1))
86}
87
88/// A parsed floating point number.
89enum Parsed {
90 /// Absolute value sig * 2^e
91 Finite {
92 neg: bool,
93 sig: u128,
94 exp: i32,
95 },
96 Infinite {
97 neg: bool,
98 },
99 Nan {
100 neg: bool,
101 },
102}
103
104/// Parse a hexadecimal float x
105const fn parse_hex(mut b: &[u8]) -> Parsed {
106 let mut neg = false;
107 let mut sig: u128 = 0;
108 let mut exp: i32 = 0;
109
110 if let &[c @ (b'-' | b'+'), ref rest @ ..] = b {
111 b = rest;
112 neg = c == b'-';
113 }
114
115 match *b {
116 [b'i' | b'I', b'n' | b'N', b'f' | b'F'] => return Parsed::Infinite { neg },
117 [b'n' | b'N', b'a' | b'A', b'n' | b'N'] => return Parsed::Nan { neg },
118 _ => (),
119 }
120
121 if let &[b'0', b'x' | b'X', ref rest @ ..] = b {
122 b = rest;
123 } else {
124 panic!("no hex indicator");
125 }
126
127 let mut seen_point = false;
128 let mut some_digits = false;
129
130 while let &[c, ref rest @ ..] = b {
131 b = rest;
132
133 match c {
134 b'.' => {
135 assert!(!seen_point);
136 seen_point = true;
137 continue;
138 }
139 b'p' | b'P' => break,
140 c => {
141 let digit = hex_digit(c);
142 some_digits = true;
143 let of;
144 (sig, of) = sig.overflowing_mul(16);
145 assert!(!of, "too many digits");
146 sig |= digit as u128;
147 // up until the fractional point, the value grows
148 // with more digits, but after it the exponent is
149 // compensated to match.
150 if seen_point {
151 exp -= 4;
152 }
153 }
154 }
155 }
156 assert!(some_digits, "at least one digit is required");
157 some_digits = false;
158
159 let mut negate_exp = false;
160 if let &[c @ (b'-' | b'+'), ref rest @ ..] = b {
161 b = rest;
162 negate_exp = c == b'-';
163 }
164
165 let mut pexp: i32 = 0;
166 while let &[c, ref rest @ ..] = b {
167 b = rest;
168 let digit = dec_digit(c);
169 some_digits = true;
170 let of;
171 (pexp, of) = pexp.overflowing_mul(10);
172 assert!(!of, "too many exponent digits");
173 pexp += digit as i32;
174 }
175 assert!(some_digits, "at least one exponent digit is required");
176
177 if negate_exp {
178 exp -= pexp;
179 } else {
180 exp += pexp;
181 }
182
183 Parsed::Finite { neg, sig, exp }
184}
185
186const fn dec_digit(c: u8) -> u8 {
187 match c {
188 b'0'..=b'9' => c - b'0',
189 _ => panic!("bad char"),
190 }
191}
192
193const fn hex_digit(c: u8) -> u8 {
194 match c {
195 b'0'..=b'9' => c - b'0',
196 b'a'..=b'f' => c - b'a' + 10,
197 b'A'..=b'F' => c - b'A' + 10,
198 _ => panic!("bad char"),
199 }
200}
201
202/* FIXME(msrv): vendor some things that are not const stable at our MSRV */
203
204/// `u128::ilog2`
205const fn u128_ilog2(v: u128) -> u32 {
206 assert!(v != 0);
207 u128::BITS - 1 - v.leading_zeros()
208}
209
210/// Format a floating point number as its IEEE hex (`%a`) representation.
211pub struct Hexf<F>(pub F);
212
213// Adapted from https://github.com/ericseppanen/hexfloat2/blob/a5c27932f0ff/src/format.rs
214#[cfg(not(feature = "compiler-builtins"))]
215fn fmt_any_hex<F: Float>(x: &F, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 if x.is_sign_negative() {
217 write!(f, "-")?;
218 }
219
220 if x.is_nan() {
221 return write!(f, "NaN");
222 } else if x.is_infinite() {
223 return write!(f, "inf");
224 } else if *x == F::ZERO {
225 return write!(f, "0x0p+0");
226 }
227
228 let mut exponent = x.exp_unbiased();
229 let sig = x.to_bits() & F::SIG_MASK;
230
231 let bias = F::EXP_BIAS as i32;
232 // The mantissa MSB needs to be shifted up to the nearest nibble.
233 let mshift = (4 - (F::SIG_BITS % 4)) % 4;
234 let sig = sig << mshift;
235 // The width is rounded up to the nearest char (4 bits)
236 let mwidth = (F::SIG_BITS as usize + 3) / 4;
237 let leading = if exponent == -bias {
238 // subnormal number means we shift our output by 1 bit.
239 exponent += 1;
240 "0."
241 } else {
242 "1."
243 };
244
245 write!(f, "0x{leading}{sig:0mwidth$x}p{exponent:+}")
246}
247
248#[cfg(feature = "compiler-builtins")]
249fn fmt_any_hex<F: Float>(_x: &F, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 unimplemented!()
251}
252
253impl<F: Float> fmt::LowerHex for Hexf<F> {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 cfg_if! {
256 if #[cfg(feature = "compiler-builtins")] {
257 let _ = f;
258 unimplemented!()
259 } else {
260 fmt_any_hex(&self.0, f)
261 }
262 }
263 }
264}
265
266impl<F: Float> fmt::LowerHex for Hexf<(F, F)> {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 cfg_if! {
269 if #[cfg(feature = "compiler-builtins")] {
270 let _ = f;
271 unimplemented!()
272 } else {
273 write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
274 }
275 }
276 }
277}
278
279impl<F: Float> fmt::LowerHex for Hexf<(F, i32)> {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 cfg_if! {
282 if #[cfg(feature = "compiler-builtins")] {
283 let _ = f;
284 unimplemented!()
285 } else {
286 write!(f, "({:x}, {:x})", Hexf(self.0.0), Hexf(self.0.1))
287 }
288 }
289 }
290}
291
292impl fmt::LowerHex for Hexf<i32> {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 cfg_if! {
295 if #[cfg(feature = "compiler-builtins")] {
296 let _ = f;
297 unimplemented!()
298 } else {
299 fmt::LowerHex::fmt(&self.0, f)
300 }
301 }
302 }
303}
304
305impl<T> fmt::Debug for Hexf<T>
306where
307 Hexf<T>: fmt::LowerHex,
308{
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 cfg_if! {
311 if #[cfg(feature = "compiler-builtins")] {
312 let _ = f;
313 unimplemented!()
314 } else {
315 fmt::LowerHex::fmt(self, f)
316 }
317 }
318 }
319}
320
321impl<T> fmt::Display for Hexf<T>
322where
323 Hexf<T>: fmt::LowerHex,
324{
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 cfg_if! {
327 if #[cfg(feature = "compiler-builtins")] {
328 let _ = f;
329 unimplemented!()
330 } else {
331 fmt::LowerHex::fmt(self, f)
332 }
333 }
334 }
335}
336
337#[cfg(test)]
338mod parse_tests {
339 extern crate std;
340 use std::{format, println};
341
342 use super::*;
343
344 #[test]
345 fn test_parse_any() {
346 for k in -149..=127 {
347 let s = format!("0x1p{k}");
348 let x = hf32(&s);
349 let y = if k < 0 { 0.5f32.powi(-k) } else { 2.0f32.powi(k) };
350 assert_eq!(x, y);
351 }
352
353 let mut s = *b"0x.0000000p-121";
354 for e in 0..40 {
355 for k in 0..(1 << 15) {
356 let expected = f32::from_bits(k) * 2.0f32.powi(e);
357 let x = hf32(std::str::from_utf8(&s).unwrap());
358 assert_eq!(
359 x.to_bits(),
360 expected.to_bits(),
361 "\
362 e={e}\n\
363 k={k}\n\
364 x={x}\n\
365 expected={expected}\n\
366 s={}\n\
367 f32::from_bits(k)={}\n\
368 2.0f32.powi(e)={}\
369 ",
370 std::str::from_utf8(&s).unwrap(),
371 f32::from_bits(k),
372 2.0f32.powi(e),
373 );
374 for i in (3..10).rev() {
375 if s[i] == b'f' {
376 s[i] = b'0';
377 } else if s[i] == b'9' {
378 s[i] = b'a';
379 break;
380 } else {
381 s[i] += 1;
382 break;
383 }
384 }
385 }
386 for i in (12..15).rev() {
387 if s[i] == b'0' {
388 s[i] = b'9';
389 } else {
390 s[i] -= 1;
391 break;
392 }
393 }
394 for i in (3..10).rev() {
395 s[i] = b'0';
396 }
397 }
398 }
399
400 // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to
401 // hide them from the AST.
402 #[cfg(f16_enabled)]
403 macro_rules! f16_tests {
404 () => {
405 #[test]
406 fn test_f16() {
407 let checks = [
408 ("0x.1234p+16", (0x1234 as f16).to_bits()),
409 ("0x1.234p+12", (0x1234 as f16).to_bits()),
410 ("0x12.34p+8", (0x1234 as f16).to_bits()),
411 ("0x123.4p+4", (0x1234 as f16).to_bits()),
412 ("0x1234p+0", (0x1234 as f16).to_bits()),
413 ("0x1234.p+0", (0x1234 as f16).to_bits()),
414 ("0x1234.0p+0", (0x1234 as f16).to_bits()),
415 ("0x1.ffcp+15", f16::MAX.to_bits()),
416 ("0x1.0p+1", 2.0f16.to_bits()),
417 ("0x1.0p+0", 1.0f16.to_bits()),
418 ("0x1.ffp+8", 0x5ffc),
419 ("+0x1.ffp+8", 0x5ffc),
420 ("0x1p+0", 0x3c00),
421 ("0x1.998p-4", 0x2e66),
422 ("0x1.9p+6", 0x5640),
423 ("0x0.0p0", 0.0f16.to_bits()),
424 ("-0x0.0p0", (-0.0f16).to_bits()),
425 ("0x1.0p0", 1.0f16.to_bits()),
426 ("0x1.998p-4", (0.1f16).to_bits()),
427 ("-0x1.998p-4", (-0.1f16).to_bits()),
428 ("0x0.123p-12", 0x0123),
429 ("0x1p-24", 0x0001),
430 ("nan", f16::NAN.to_bits()),
431 ("-nan", (-f16::NAN).to_bits()),
432 ("inf", f16::INFINITY.to_bits()),
433 ("-inf", f16::NEG_INFINITY.to_bits()),
434 ];
435 for (s, exp) in checks {
436 println!("parsing {s}");
437 let act = hf16(s).to_bits();
438 assert_eq!(
439 act, exp,
440 "parsing {s}: {act:#06x} != {exp:#06x}\nact: {act:#018b}\nexp: {exp:#018b}"
441 );
442 }
443 }
444
445 #[test]
446 fn test_macros_f16() {
447 assert_eq!(hf16!("0x1.ffp+8").to_bits(), 0x5ffc_u16);
448 }
449 };
450 }
451
452 #[cfg(f16_enabled)]
453 f16_tests!();
454
455 #[test]
456 fn test_f32() {
457 let checks = [
458 ("0x.1234p+16", (0x1234 as f32).to_bits()),
459 ("0x1.234p+12", (0x1234 as f32).to_bits()),
460 ("0x12.34p+8", (0x1234 as f32).to_bits()),
461 ("0x123.4p+4", (0x1234 as f32).to_bits()),
462 ("0x1234p+0", (0x1234 as f32).to_bits()),
463 ("0x1234.p+0", (0x1234 as f32).to_bits()),
464 ("0x1234.0p+0", (0x1234 as f32).to_bits()),
465 ("0x1.fffffep+127", f32::MAX.to_bits()),
466 ("0x1.0p+1", 2.0f32.to_bits()),
467 ("0x1.0p+0", 1.0f32.to_bits()),
468 ("0x1.ffep+8", 0x43fff000),
469 ("+0x1.ffep+8", 0x43fff000),
470 ("0x1p+0", 0x3f800000),
471 ("0x1.99999ap-4", 0x3dcccccd),
472 ("0x1.9p+6", 0x42c80000),
473 ("0x1.2d5ed2p+20", 0x4996af69),
474 ("-0x1.348eb8p+10", 0xc49a475c),
475 ("-0x1.33dcfep-33", 0xaf19ee7f),
476 ("0x0.0p0", 0.0f32.to_bits()),
477 ("-0x0.0p0", (-0.0f32).to_bits()),
478 ("0x1.0p0", 1.0f32.to_bits()),
479 ("0x1.99999ap-4", (0.1f32).to_bits()),
480 ("-0x1.99999ap-4", (-0.1f32).to_bits()),
481 ("0x1.111114p-127", 0x00444445),
482 ("0x1.23456p-130", 0x00091a2b),
483 ("0x1p-149", 0x00000001),
484 ("nan", f32::NAN.to_bits()),
485 ("-nan", (-f32::NAN).to_bits()),
486 ("inf", f32::INFINITY.to_bits()),
487 ("-inf", f32::NEG_INFINITY.to_bits()),
488 ];
489 for (s, exp) in checks {
490 println!("parsing {s}");
491 let act = hf32(s).to_bits();
492 assert_eq!(
493 act, exp,
494 "parsing {s}: {act:#010x} != {exp:#010x}\nact: {act:#034b}\nexp: {exp:#034b}"
495 );
496 }
497 }
498
499 #[test]
500 fn test_f64() {
501 let checks = [
502 ("0x.1234p+16", (0x1234 as f64).to_bits()),
503 ("0x1.234p+12", (0x1234 as f64).to_bits()),
504 ("0x12.34p+8", (0x1234 as f64).to_bits()),
505 ("0x123.4p+4", (0x1234 as f64).to_bits()),
506 ("0x1234p+0", (0x1234 as f64).to_bits()),
507 ("0x1234.p+0", (0x1234 as f64).to_bits()),
508 ("0x1234.0p+0", (0x1234 as f64).to_bits()),
509 ("0x1.ffep+8", 0x407ffe0000000000),
510 ("0x1p+0", 0x3ff0000000000000),
511 ("0x1.999999999999ap-4", 0x3fb999999999999a),
512 ("0x1.9p+6", 0x4059000000000000),
513 ("0x1.2d5ed1fe1da7bp+20", 0x4132d5ed1fe1da7b),
514 ("-0x1.348eb851eb852p+10", 0xc09348eb851eb852),
515 ("-0x1.33dcfe54a3803p-33", 0xbde33dcfe54a3803),
516 ("0x1.0p0", 1.0f64.to_bits()),
517 ("0x0.0p0", 0.0f64.to_bits()),
518 ("-0x0.0p0", (-0.0f64).to_bits()),
519 ("0x1.999999999999ap-4", 0.1f64.to_bits()),
520 ("0x1.999999999998ap-4", (0.1f64 - f64::EPSILON).to_bits()),
521 ("-0x1.999999999999ap-4", (-0.1f64).to_bits()),
522 ("-0x1.999999999998ap-4", (-0.1f64 + f64::EPSILON).to_bits()),
523 ("0x0.8000000000001p-1022", 0x0008000000000001),
524 ("0x0.123456789abcdp-1022", 0x000123456789abcd),
525 ("0x0.0000000000002p-1022", 0x0000000000000002),
526 ("nan", f64::NAN.to_bits()),
527 ("-nan", (-f64::NAN).to_bits()),
528 ("inf", f64::INFINITY.to_bits()),
529 ("-inf", f64::NEG_INFINITY.to_bits()),
530 ];
531 for (s, exp) in checks {
532 println!("parsing {s}");
533 let act = hf64(s).to_bits();
534 assert_eq!(
535 act, exp,
536 "parsing {s}: {act:#018x} != {exp:#018x}\nact: {act:#066b}\nexp: {exp:#066b}"
537 );
538 }
539 }
540
541 // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to
542 // hide them from the AST.
543 #[cfg(f128_enabled)]
544 macro_rules! f128_tests {
545 () => {
546 #[test]
547 fn test_f128() {
548 let checks = [
549 ("0x.1234p+16", (0x1234 as f128).to_bits()),
550 ("0x1.234p+12", (0x1234 as f128).to_bits()),
551 ("0x12.34p+8", (0x1234 as f128).to_bits()),
552 ("0x123.4p+4", (0x1234 as f128).to_bits()),
553 ("0x1234p+0", (0x1234 as f128).to_bits()),
554 ("0x1234.p+0", (0x1234 as f128).to_bits()),
555 ("0x1234.0p+0", (0x1234 as f128).to_bits()),
556 ("0x1.ffffffffffffffffffffffffffffp+16383", f128::MAX.to_bits()),
557 ("0x1.0p+1", 2.0f128.to_bits()),
558 ("0x1.0p+0", 1.0f128.to_bits()),
559 ("0x1.ffep+8", 0x4007ffe0000000000000000000000000),
560 ("+0x1.ffep+8", 0x4007ffe0000000000000000000000000),
561 ("0x1p+0", 0x3fff0000000000000000000000000000),
562 ("0x1.999999999999999999999999999ap-4", 0x3ffb999999999999999999999999999a),
563 ("0x1.9p+6", 0x40059000000000000000000000000000),
564 ("0x0.0p0", 0.0f128.to_bits()),
565 ("-0x0.0p0", (-0.0f128).to_bits()),
566 ("0x1.0p0", 1.0f128.to_bits()),
567 ("0x1.999999999999999999999999999ap-4", (0.1f128).to_bits()),
568 ("-0x1.999999999999999999999999999ap-4", (-0.1f128).to_bits()),
569 ("0x0.abcdef0123456789abcdef012345p-16382", 0x0000abcdef0123456789abcdef012345),
570 ("0x1p-16494", 0x00000000000000000000000000000001),
571 ("nan", f128::NAN.to_bits()),
572 ("-nan", (-f128::NAN).to_bits()),
573 ("inf", f128::INFINITY.to_bits()),
574 ("-inf", f128::NEG_INFINITY.to_bits()),
575 ];
576 for (s, exp) in checks {
577 println!("parsing {s}");
578 let act = hf128(s).to_bits();
579 assert_eq!(
580 act, exp,
581 "parsing {s}: {act:#034x} != {exp:#034x}\nact: {act:#0130b}\nexp: {exp:#0130b}"
582 );
583 }
584 }
585
586 #[test]
587 fn test_macros_f128() {
588 assert_eq!(hf128!("0x1.ffep+8").to_bits(), 0x4007ffe0000000000000000000000000_u128);
589 }
590 }
591 }
592
593 #[cfg(f128_enabled)]
594 f128_tests!();
595
596 #[test]
597 fn test_macros() {
598 #[cfg(f16_enabled)]
599 assert_eq!(hf16!("0x1.ffp+8").to_bits(), 0x5ffc_u16);
600 assert_eq!(hf32!("0x1.ffep+8").to_bits(), 0x43fff000_u32);
601 assert_eq!(hf64!("0x1.ffep+8").to_bits(), 0x407ffe0000000000_u64);
602 #[cfg(f128_enabled)]
603 assert_eq!(hf128!("0x1.ffep+8").to_bits(), 0x4007ffe0000000000000000000000000_u128);
604 }
605}
606
607#[cfg(test)]
608// FIXME(ppc): something with `should_panic` tests cause a SIGILL with ppc64le
609#[cfg(not(all(target_arch = "powerpc64", target_endian = "little")))]
610mod tests_panicking {
611 extern crate std;
612 use super::*;
613
614 // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to
615 // hide them from the AST.
616 #[cfg(f16_enabled)]
617 macro_rules! f16_tests {
618 () => {
619 #[test]
620 fn test_f16_almost_extra_precision() {
621 // Exact maximum precision allowed
622 hf16("0x1.ffcp+0");
623 }
624
625 #[test]
626 #[should_panic(expected = "the value is too precise")]
627 fn test_f16_extra_precision() {
628 // One bit more than the above.
629 hf16("0x1.ffdp+0");
630 }
631
632 #[test]
633 #[should_panic(expected = "the value is too huge")]
634 fn test_f16_overflow() {
635 // One bit more than the above.
636 hf16("0x1p+16");
637 }
638
639 #[test]
640 fn test_f16_tiniest() {
641 let x = hf16("0x1.p-24");
642 let y = hf16("0x0.001p-12");
643 let z = hf16("0x0.8p-23");
644 assert_eq!(x, y);
645 assert_eq!(x, z);
646 }
647
648 #[test]
649 #[should_panic(expected = "the value is too tiny")]
650 fn test_f16_too_tiny() {
651 hf16("0x1.p-25");
652 }
653
654 #[test]
655 #[should_panic(expected = "the value is too tiny")]
656 fn test_f16_also_too_tiny() {
657 hf16("0x0.8p-24");
658 }
659
660 #[test]
661 #[should_panic(expected = "the value is too tiny")]
662 fn test_f16_again_too_tiny() {
663 hf16("0x0.001p-13");
664 }
665 };
666 }
667
668 #[cfg(f16_enabled)]
669 f16_tests!();
670
671 #[test]
672 fn test_f32_almost_extra_precision() {
673 // Exact maximum precision allowed
674 hf32("0x1.abcdeep+0");
675 }
676
677 #[test]
678 #[should_panic]
679 fn test_f32_extra_precision2() {
680 // One bit more than the above.
681 hf32("0x1.ffffffp+127");
682 }
683
684 #[test]
685 #[should_panic(expected = "the value is too huge")]
686 fn test_f32_overflow() {
687 // One bit more than the above.
688 hf32("0x1p+128");
689 }
690
691 #[test]
692 #[should_panic(expected = "the value is too precise")]
693 fn test_f32_extra_precision() {
694 // One bit more than the above.
695 hf32("0x1.abcdefp+0");
696 }
697
698 #[test]
699 fn test_f32_tiniest() {
700 let x = hf32("0x1.p-149");
701 let y = hf32("0x0.0000000000000001p-85");
702 let z = hf32("0x0.8p-148");
703 assert_eq!(x, y);
704 assert_eq!(x, z);
705 }
706
707 #[test]
708 #[should_panic(expected = "the value is too tiny")]
709 fn test_f32_too_tiny() {
710 hf32("0x1.p-150");
711 }
712
713 #[test]
714 #[should_panic(expected = "the value is too tiny")]
715 fn test_f32_also_too_tiny() {
716 hf32("0x0.8p-149");
717 }
718
719 #[test]
720 #[should_panic(expected = "the value is too tiny")]
721 fn test_f32_again_too_tiny() {
722 hf32("0x0.0000000000000001p-86");
723 }
724
725 #[test]
726 fn test_f64_almost_extra_precision() {
727 // Exact maximum precision allowed
728 hf64("0x1.abcdabcdabcdfp+0");
729 }
730
731 #[test]
732 #[should_panic(expected = "the value is too precise")]
733 fn test_f64_extra_precision() {
734 // One bit more than the above.
735 hf64("0x1.abcdabcdabcdf8p+0");
736 }
737
738 // HACK(msrv): 1.63 rejects unknown width float literals at an AST level, so use a macro to
739 // hide them from the AST.
740 #[cfg(f128_enabled)]
741 macro_rules! f128_tests {
742 () => {
743 #[test]
744 fn test_f128_almost_extra_precision() {
745 // Exact maximum precision allowed
746 hf128("0x1.ffffffffffffffffffffffffffffp+16383");
747 }
748
749 #[test]
750 #[should_panic(expected = "the value is too precise")]
751 fn test_f128_extra_precision() {
752 // One bit more than the above.
753 hf128("0x1.ffffffffffffffffffffffffffff8p+16383");
754 }
755
756 #[test]
757 #[should_panic(expected = "the value is too huge")]
758 fn test_f128_overflow() {
759 // One bit more than the above.
760 hf128("0x1p+16384");
761 }
762
763 #[test]
764 fn test_f128_tiniest() {
765 let x = hf128("0x1.p-16494");
766 let y = hf128("0x0.0000000000000001p-16430");
767 let z = hf128("0x0.8p-16493");
768 assert_eq!(x, y);
769 assert_eq!(x, z);
770 }
771
772 #[test]
773 #[should_panic(expected = "the value is too tiny")]
774 fn test_f128_too_tiny() {
775 hf128("0x1.p-16495");
776 }
777
778 #[test]
779 #[should_panic(expected = "the value is too tiny")]
780 fn test_f128_again_too_tiny() {
781 hf128("0x0.0000000000000001p-16431");
782 }
783
784 #[test]
785 #[should_panic(expected = "the value is too tiny")]
786 fn test_f128_also_too_tiny() {
787 hf128("0x0.8p-16494");
788 }
789 };
790 }
791
792 #[cfg(f128_enabled)]
793 f128_tests!();
794}
795
796#[cfg(test)]
797mod print_tests {
798 extern crate std;
799 use std::string::ToString;
800
801 use super::*;
802
803 #[test]
804 #[cfg(f16_enabled)]
805 fn test_f16() {
806 use std::format;
807 // Exhaustively check that `f16` roundtrips.
808 for x in 0..=u16::MAX {
809 let f = f16::from_bits(x);
810 let s = format!("{}", Hexf(f));
811 let from_s = hf16(&s);
812
813 if f.is_nan() && from_s.is_nan() {
814 continue;
815 }
816
817 assert_eq!(
818 f.to_bits(),
819 from_s.to_bits(),
820 "{f:?} formatted as {s} but parsed as {from_s:?}"
821 );
822 }
823 }
824
825 #[test]
826 fn spot_checks() {
827 assert_eq!(Hexf(f32::MAX).to_string(), "0x1.fffffep+127");
828 assert_eq!(Hexf(f64::MAX).to_string(), "0x1.fffffffffffffp+1023");
829
830 assert_eq!(Hexf(f32::MIN).to_string(), "-0x1.fffffep+127");
831 assert_eq!(Hexf(f64::MIN).to_string(), "-0x1.fffffffffffffp+1023");
832
833 assert_eq!(Hexf(f32::ZERO).to_string(), "0x0p+0");
834 assert_eq!(Hexf(f64::ZERO).to_string(), "0x0p+0");
835
836 assert_eq!(Hexf(f32::NEG_ZERO).to_string(), "-0x0p+0");
837 assert_eq!(Hexf(f64::NEG_ZERO).to_string(), "-0x0p+0");
838
839 assert_eq!(Hexf(f32::NAN).to_string(), "NaN");
840 assert_eq!(Hexf(f64::NAN).to_string(), "NaN");
841
842 assert_eq!(Hexf(f32::INFINITY).to_string(), "inf");
843 assert_eq!(Hexf(f64::INFINITY).to_string(), "inf");
844
845 assert_eq!(Hexf(f32::NEG_INFINITY).to_string(), "-inf");
846 assert_eq!(Hexf(f64::NEG_INFINITY).to_string(), "-inf");
847
848 #[cfg(f16_enabled)]
849 {
850 assert_eq!(Hexf(f16::MAX).to_string(), "0x1.ffcp+15");
851 assert_eq!(Hexf(f16::MIN).to_string(), "-0x1.ffcp+15");
852 assert_eq!(Hexf(f16::ZERO).to_string(), "0x0p+0");
853 assert_eq!(Hexf(f16::NEG_ZERO).to_string(), "-0x0p+0");
854 assert_eq!(Hexf(f16::NAN).to_string(), "NaN");
855 assert_eq!(Hexf(f16::INFINITY).to_string(), "inf");
856 assert_eq!(Hexf(f16::NEG_INFINITY).to_string(), "-inf");
857 }
858
859 #[cfg(f128_enabled)]
860 {
861 assert_eq!(Hexf(f128::MAX).to_string(), "0x1.ffffffffffffffffffffffffffffp+16383");
862 assert_eq!(Hexf(f128::MIN).to_string(), "-0x1.ffffffffffffffffffffffffffffp+16383");
863 assert_eq!(Hexf(f128::ZERO).to_string(), "0x0p+0");
864 assert_eq!(Hexf(f128::NEG_ZERO).to_string(), "-0x0p+0");
865 assert_eq!(Hexf(f128::NAN).to_string(), "NaN");
866 assert_eq!(Hexf(f128::INFINITY).to_string(), "inf");
867 assert_eq!(Hexf(f128::NEG_INFINITY).to_string(), "-inf");
868 }
869 }
870}
871