Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

Commit f591003

Browse files
committed
Add a const function and macro for parsing hex floats
1 parent e95b1f7 commit f591003

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed

crates/compiler-builtins-smoke-test/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@
99

1010
#[path = "../../../src/math/mod.rs"]
1111
pub mod libm;
12+
13+
mod math {
14+
pub use super::libm::*;
15+
}

src/math/mod.rs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ macro_rules! llvm_intrinsically_optimized {
8585
};
8686
}
8787

88+
#[allow(unused)]
89+
macro_rules! hf32 {
90+
($s:literal) => {
91+
const { $crate::math::hex_float::hf32($s) }
92+
};
93+
}
94+
95+
#[allow(unused)]
96+
macro_rules! hf64 {
97+
($s:literal) => {
98+
const { $crate::math::hex_float::hf64($s) }
99+
};
100+
}
101+
88102
// Public modules
89103
mod acos;
90104
mod acosf;
@@ -369,3 +383,235 @@ fn with_set_low_word(f: f64, lo: u32) -> f64 {
369383
fn combine_words(hi: u32, lo: u32) -> f64 {
370384
f64::from_bits((hi as u64) << 32 | lo as u64)
371385
}
386+
387+
pub mod hex_float {
388+
pub const fn hf32(s: &str) -> f32 {
389+
f32::from_bits(parse_any(s, 32, 23) as u32)
390+
}
391+
392+
pub const fn hf64(s: &str) -> f64 {
393+
f64::from_bits(parse_any(s, 64, 52) as u64)
394+
}
395+
396+
const fn parse_any(s: &str, bits: u32, sig_bits: u32) -> u128 {
397+
let exp_bits: u32 = bits - sig_bits - 1;
398+
let exp_max: i32 = (1 << exp_bits) - 2;
399+
let exp_bias: i32 = (1 << (exp_bits - 1)) - 1;
400+
401+
let b = s.as_bytes();
402+
403+
let mut exp: i32 = parse_exp(b) + exp_bias;
404+
assert!(exp <= exp_max);
405+
406+
// Out of range exponents are subnormal, this changes
407+
// how we shift bits in.
408+
let subnorm_shift = if exp <= 0 {
409+
let tmp = -exp + 1;
410+
exp = 0;
411+
tmp
412+
} else {
413+
0
414+
};
415+
416+
let mut i;
417+
let mut next_i = 0;
418+
let mut sig: u128 = 0;
419+
let mut neg: u128 = 0;
420+
421+
// Position relative to digits
422+
let mut digit_idx = 0;
423+
let mut start_0x = 0;
424+
let mut start_digits;
425+
let mut maybe_zero = true;
426+
427+
while next_i < b.len() {
428+
next_i += 1;
429+
i = next_i - 1;
430+
let c = b[i];
431+
432+
if i == 0 && c == b'-' {
433+
neg = 1;
434+
start_0x += 1;
435+
continue;
436+
} else if i == 0 && c == b'+' {
437+
start_0x += 1;
438+
continue;
439+
}
440+
441+
start_digits = start_0x + 2;
442+
443+
if (i - start_0x) == 0 {
444+
assert!(c == b'0');
445+
continue;
446+
} else if (i - start_0x) == 1 {
447+
assert!(c == b'x');
448+
continue;
449+
}
450+
451+
if c == b'p' {
452+
break;
453+
} else if (i - start_digits) == 0 {
454+
if c == b'0' {
455+
exp -= 1;
456+
} else {
457+
assert!(c == b'1');
458+
maybe_zero = false;
459+
}
460+
} else if (i - start_digits) == 1 {
461+
assert!(c == b'.');
462+
continue;
463+
} else {
464+
digit_idx += 1;
465+
}
466+
467+
let nibbles = digit_idx * 4;
468+
let shift = sig_bits as i32 - subnorm_shift - nibbles;
469+
let d = hex_digit(c);
470+
if d != 0 {
471+
maybe_zero = false;
472+
}
473+
474+
if shift > 0 {
475+
// Shift the hex digits into the mantissa
476+
// Note: the implicit bit is included and needs to be cleared
477+
// later (leading `1.` gets parsed as the implicit bit)
478+
sig |= (d as u128) << shift;
479+
} else if shift > -4 {
480+
// Shift right up to 4 bits
481+
sig |= (d as u128) >> -shift;
482+
}
483+
}
484+
485+
if maybe_zero {
486+
sig = 0;
487+
exp = 0;
488+
}
489+
490+
// Clear the implicit bit
491+
sig = (sig << (128 - sig_bits)) >> (128 - sig_bits);
492+
493+
// Construct a float
494+
(neg << (bits - 1)) | ((exp as u128) << sig_bits) | sig
495+
}
496+
497+
const fn parse_exp(b: &[u8]) -> i32 {
498+
let mut i = 0;
499+
while i < b.len() {
500+
if b[i] == b'p' {
501+
i += 1;
502+
break;
503+
}
504+
i += 1;
505+
}
506+
507+
let mut ret: i32 = 0;
508+
let mut neg = false;
509+
let mut next_i = i;
510+
let start = i;
511+
512+
while next_i < b.len() {
513+
next_i += 1;
514+
i = next_i - 1;
515+
let c = b[i];
516+
517+
if i == start && c == b'-' {
518+
neg = true;
519+
continue;
520+
} else if i == start && c == b'+' {
521+
continue;
522+
}
523+
524+
let d = dec_digit(c);
525+
ret *= 10;
526+
ret += d as i32;
527+
}
528+
529+
if neg {
530+
ret *= -1;
531+
}
532+
533+
ret
534+
}
535+
536+
const fn hex_digit(c: u8) -> u8 {
537+
match (c as char).to_digit(16) {
538+
Some(v) => v as u8,
539+
None => panic!("bad char"),
540+
}
541+
}
542+
543+
const fn dec_digit(c: u8) -> u8 {
544+
match (c as char).to_digit(10) {
545+
Some(v) => v as u8,
546+
None => panic!("bad char"),
547+
}
548+
}
549+
550+
#[cfg(test)]
551+
mod tests {
552+
extern crate std;
553+
use super::*;
554+
use std::println;
555+
556+
#[test]
557+
fn test_f32() {
558+
let checks = [
559+
("0x1.ffep+8", 0x43fff000),
560+
("+0x1.ffep+8", 0x43fff000),
561+
("0x1p+0", 0x3f800000),
562+
("0x1.99999ap-4", 0x3dcccccd),
563+
("0x1.9p+6", 0x42c80000),
564+
("0x1.2d5ed2p+20", 0x4996af69),
565+
("-0x1.348eb8p+10", 0xc49a475c),
566+
("-0x1.33dcfep-33", 0xaf19ee7f),
567+
("0x0.0p0", 0.0f32.to_bits()),
568+
("-0x0.0p0", (-0.0f32).to_bits()),
569+
("0x1.0p0", 1.0f32.to_bits()),
570+
("0x1.99999ap-4", (0.1f32).to_bits()),
571+
("-0x1.99999ap-4", (-0.1f32).to_bits()),
572+
("0x1.111114p-127", 0x00444445),
573+
("0x1.23456p-130", 0x00091a2b),
574+
("0x1p-149", 0x00000001),
575+
];
576+
for (s, exp) in checks {
577+
println!("parsing {s}");
578+
let act = hf32(s).to_bits();
579+
assert_eq!(
580+
act, exp,
581+
"parsing {s}: {act:#010x} != {exp:#010x}\nact: {act:#034b}\nexp: {exp:#034b}"
582+
);
583+
}
584+
}
585+
586+
#[test]
587+
fn test_f64() {
588+
let checks = [
589+
("0x1.ffep+8", 0x407ffe0000000000),
590+
("0x1p+0", 0x3ff0000000000000),
591+
("0x1.999999999999ap-4", 0x3fb999999999999a),
592+
("0x1.9p+6", 0x4059000000000000),
593+
("0x1.2d5ed1fe1da7bp+20", 0x4132d5ed1fe1da7b),
594+
("-0x1.348eb851eb852p+10", 0xc09348eb851eb852),
595+
("-0x1.33dcfe54a3803p-33", 0xbde33dcfe54a3803),
596+
("0x1.0p0", 1.0f64.to_bits()),
597+
("0x0.0p0", 0.0f64.to_bits()),
598+
("-0x0.0p0", (-0.0f64).to_bits()),
599+
("0x1.999999999999ap-4", 0.1f64.to_bits()),
600+
("0x1.999999999998ap-4", (0.1f64 - f64::EPSILON).to_bits()),
601+
("-0x1.999999999999ap-4", (-0.1f64).to_bits()),
602+
("-0x1.999999999998ap-4", (-0.1f64 + f64::EPSILON).to_bits()),
603+
("0x0.8000000000001p-1022", 0x0008000000000001),
604+
("0x0.123456789abcdp-1022", 0x000123456789abcd),
605+
("0x0.0000000000002p-1022", 0x0000000000000002),
606+
];
607+
for (s, exp) in checks {
608+
println!("parsing {s}");
609+
let act = hf64(s).to_bits();
610+
assert_eq!(
611+
act, exp,
612+
"parsing {s}: {act:#018x} != {exp:#018x}\nact: {act:#066b}\nexp: {exp:#066b}"
613+
);
614+
}
615+
}
616+
}
617+
}

0 commit comments

Comments
 (0)