Repository: uutils/coreutils (language: rust)

Issue:
printf: `%a` output exponent is incorrect (should be decimal, not hexadecimal)
`%a` should output "Hexadecimal floating point, lowercase"

For small numbers below 1, the exponent is printed incorrectly:
```
$ cargo run printf "%a\n" 0.5
0x1.0000000000000p+ffffffffffffffff /// This should be 0x1.0000000000000p-1
$ printf "%a\n" 0.5
0x8p-4
```

We'll cover issues with shifting (0x8 or 0x1) and padding in another issue.


Discussion / hints:

Fix patch:
diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs
index caee8e30374..0acec0598a1 100644
--- a/src/uucore/src/lib/features/format/num_format.rs
+++ b/src/uucore/src/lib/features/format/num_format.rs
@@ -465,20 +465,21 @@ fn format_float_hexadecimal(
     case: Case,
     force_decimal: ForceDecimal,
 ) -> String {
-    let (first_digit, mantissa, exponent) = if f == 0.0 {
-        (0, 0, 0)
+    let (sign, first_digit, mantissa, exponent) = if f == 0.0 {
+        ("", 0, 0, 0)
     } else {
         let bits = f.to_bits();
-        let exponent_bits = ((bits >> 52) & 0x7fff) as i64;
+        let sign = if (bits >> 63) == 1 { "-" } else { "" };
+        let exponent_bits = ((bits >> 52) & 0x7ff) as i64;
         let exponent = exponent_bits - 1023;
         let mantissa = bits & 0xf_ffff_ffff_ffff;
-        (1, mantissa, exponent)
+        (sign, 1, mantissa, exponent)
     };
 
     let mut s = match (precision, force_decimal) {
-        (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"),
-        (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"),
-        _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"),
+        (0, ForceDecimal::No) => format!("{sign}0x{first_digit}p{exponent:+}"),
+        (0, ForceDecimal::Yes) => format!("{sign}0x{first_digit}.p{exponent:+}"),
+        _ => format!("{sign}0x{first_digit}.{mantissa:0>13x}p{exponent:+}"),
     };
 
     if case == Case::Uppercase {
@@ -654,6 +655,33 @@ mod test {
         assert_eq!(f(99_999_999.0), "1.e+08");
     }
 
+    #[test]
+    fn hexadecimal_float() {
+        use super::format_float_hexadecimal;
+        let f = |x| format_float_hexadecimal(x, 6, Case::Lowercase, ForceDecimal::No);
+        // TODO(#7364): These values do not match coreutils output, but are possible correct representations.
+        assert_eq!(f(0.00001), "0x1.4f8b588e368f1p-17");
+        assert_eq!(f(0.125), "0x1.0000000000000p-3");
+        assert_eq!(f(256.0), "0x1.0000000000000p+8");
+        assert_eq!(f(65536.0), "0x1.0000000000000p+16");
+        assert_eq!(f(-0.00001), "-0x1.4f8b588e368f1p-17");
+        assert_eq!(f(-0.125), "-0x1.0000000000000p-3");
+        assert_eq!(f(-256.0), "-0x1.0000000000000p+8");
+        assert_eq!(f(-65536.0), "-0x1.0000000000000p+16");
+
+        let f = |x| format_float_hexadecimal(x, 0, Case::Lowercase, ForceDecimal::No);
+        assert_eq!(f(0.125), "0x1p-3");
+        assert_eq!(f(256.0), "0x1p+8");
+        assert_eq!(f(-0.125), "-0x1p-3");
+        assert_eq!(f(-256.0), "-0x1p+8");
+
+        let f = |x| format_float_hexadecimal(x, 0, Case::Lowercase, ForceDecimal::Yes);
+        assert_eq!(f(0.125), "0x1.p-3");
+        assert_eq!(f(256.0), "0x1.p+8");
+        assert_eq!(f(-0.125), "-0x1.p-3");
+        assert_eq!(f(-256.0), "-0x1.p+8");
+    }
+
     #[test]
     fn strip_insignificant_end() {
         use super::strip_fractional_zeroes_and_dot;


Test patch: