This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 2101a1a4114093a5ecc94905248d43b0bb37fc90
Author: Marco Casaroli <[email protected]>
AuthorDate: Thu Nov 5 15:51:50 2026 +0100

    libs/libc/stream: Add an option for %a and %A in printf().
    
    printf() read the argument of %a and printed "?".  Add the option
    LIBC_PRINTF_HEXFLOAT.  It is on in the Full profile, and off in the
    Minimal and Default profiles, which keep "?".
    
    The conversion takes the sign, the exponent and the mantissa from the
    bits of the value, and rounds on the hexadecimal digits to nearest, ties
    to even.  It needs no floating point operation and no big numbers, so it
    does not depend on LIBC_FLOATINGPOINT and it pulls in no soft-float code.
    All 64-bit shifts have a constant count.
    
    A long double has its own digits where it has more bits than a double:
    binary128, and the 80-bit format of x86.  With numbered arguments on, the
    union of the arguments then also holds a long double.  %a of a subnormal
    value has the leading digit 0 and the smallest exponent, as in GNU libc.
    
    Part of https://github.com/apache/nuttx/issues/21467
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 libs/libc/stdio/Kconfig            |   9 ++
 libs/libc/stream/CMakeLists.txt    |   4 +
 libs/libc/stream/Make.defs         |   4 +
 libs/libc/stream/lib_hexfloat.c    | 175 ++++++++++++++++++++++++++++++++
 libs/libc/stream/lib_hexfloat.h    |  83 ++++++++++++++++
 libs/libc/stream/lib_libvsprintf.c | 198 ++++++++++++++++++++++++++++++++++++-
 6 files changed, 470 insertions(+), 3 deletions(-)

diff --git a/libs/libc/stdio/Kconfig b/libs/libc/stdio/Kconfig
index 4d41c5484f4..342063804da 100644
--- a/libs/libc/stdio/Kconfig
+++ b/libs/libc/stdio/Kconfig
@@ -130,6 +130,15 @@ config LIBC_PRINTF_SHORT
                Enables the conversion of the value to short or char, as in %hd 
and
                %hhx.  Without it, printf() reads the argument and prints "?".
 
+config LIBC_PRINTF_HEXFLOAT
+       bool "Enable %a and %A in printf"
+       default LIBC_PRINTF_FULL
+       ---help---
+               Enables the output of %a and %A: the exact value in 
hexadecimal, such
+               as 0x1.8p+0 for 1.5.  It needs no floating point operation, so 
it
+               does not need LIBC_FLOATINGPOINT and it pulls in no soft-float 
code.
+               Without it, printf() reads the argument and prints "?".
+
 config LIBC_FLOATINGPOINT
        bool "Enable floating point in printf"
        default LIBC_PRINTF_FULL || (!DEFAULT_SMALL && ARCH_FPU && 
!LIBC_PRINTF_MINIMAL)
diff --git a/libs/libc/stream/CMakeLists.txt b/libs/libc/stream/CMakeLists.txt
index 5ac8d833995..c6a811ffb78 100644
--- a/libs/libc/stream/CMakeLists.txt
+++ b/libs/libc/stream/CMakeLists.txt
@@ -57,6 +57,10 @@ if(CONFIG_LIBC_FLOATINGPOINT)
   list(APPEND SRCS lib_dtoa_engine.c lib_dtoa_data.c)
 endif()
 
+if(CONFIG_LIBC_PRINTF_HEXFLOAT)
+  list(APPEND SRCS lib_hexfloat.c)
+endif()
+
 if(CONFIG_FILE_STREAM)
   list(APPEND SRCS lib_stdinstream.c lib_stdoutstream.c lib_stdsistream.c
        lib_stdsostream.c)
diff --git a/libs/libc/stream/Make.defs b/libs/libc/stream/Make.defs
index 3f821272c08..dbd61f99874 100644
--- a/libs/libc/stream/Make.defs
+++ b/libs/libc/stream/Make.defs
@@ -41,6 +41,10 @@ ifeq ($(CONFIG_LIBC_FLOATINGPOINT),y)
 CSRCS += lib_dtoa_engine.c lib_dtoa_data.c
 endif
 
+ifeq ($(CONFIG_LIBC_PRINTF_HEXFLOAT),y)
+CSRCS += lib_hexfloat.c
+endif
+
 # The remaining sources files depend upon C streams
 
 ifeq ($(CONFIG_FILE_STREAM),y)
diff --git a/libs/libc/stream/lib_hexfloat.c b/libs/libc/stream/lib_hexfloat.c
new file mode 100644
index 00000000000..e20d21f77d5
--- /dev/null
+++ b/libs/libc/stream/lib_hexfloat.c
@@ -0,0 +1,175 @@
+/****************************************************************************
+ * libs/libc/stream/lib_hexfloat.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <string.h>
+
+#include "lib_hexfloat.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* A double has 64 bits, or 32 bits on some small CPUs */
+
+#if DBL_MANT_DIG > 24
+typedef uint64_t hexfloat_dbits_t;
+#else
+typedef uint32_t hexfloat_dbits_t;
+#endif
+
+#ifdef CONFIG_ENDIAN_BIG
+#  define HEXFLOAT_HI 0
+#  define HEXFLOAT_LO 1
+#else
+#  define HEXFLOAT_HI 1
+#  define HEXFLOAT_LO 0
+#endif
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/* hi and lo hold the bits after the point, from the top.  All 64-bit shifts
+ * have a constant count, so a 32-bit CPU needs no libgcc call.
+ */
+
+static void hexfloat_convert(FAR struct hexfloat_s *hex, bool neg, int exp,
+                             int expmax, uint64_t hi, uint64_t lo, int ndigs,
+                             int prec, bool upper)
+{
+  int i;
+
+  hex->flags = neg ? HEXFLOAT_MINUS : 0;
+  hex->digits[0] = 1;
+
+  if (exp == expmax)
+    {
+      hex->flags |= (hi | lo) != 0 ? HEXFLOAT_NAN : HEXFLOAT_INF;
+      return;
+    }
+
+  if (exp == 0)
+    {
+      /* Zero, or a subnormal value with the exponent of the smallest normal
+       * value.
+       */
+
+      hex->digits[0] = 0;
+      exp = (hi | lo) != 0;
+    }
+
+  hex->exp = (hex->digits[0] | exp) != 0 ? exp - expmax / 2 : 0;
+
+  for (i = 1; i <= ndigs; i++)
+    {
+      hex->digits[i] = hi >> 60;
+      hi = (hi << 4) | (lo >> 60);
+      lo <<= 4;
+    }
+
+  if (prec >= 0 && prec < ndigs)
+    {
+      unsigned char rest = 0;
+
+      /* Round to nearest, ties to even */
+
+      for (i = prec + 2; i <= ndigs; i++)
+        {
+          rest |= hex->digits[i];
+        }
+
+      if (hex->digits[prec + 1] > 8 ||
+          (hex->digits[prec + 1] == 8 &&
+           (rest != 0 || (hex->digits[prec] & 1) != 0)))
+        {
+          for (i = prec; i > 0 && hex->digits[i] == 15; i--)
+            {
+              hex->digits[i] = 0;
+            }
+
+          hex->digits[i]++;
+        }
+
+      ndigs = prec;
+    }
+  else if (prec < 0)
+    {
+      while (ndigs > 0 && hex->digits[ndigs] == 0)
+        {
+          ndigs--;
+        }
+    }
+
+  hex->ndigs = ndigs;
+  for (i = 0; i <= ndigs; i++)
+    {
+      hex->digits[i] += hex->digits[i] < 10 ? '0' :
+                        (upper ? 'A' : 'a') - 10;
+    }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+void __hexfloat_engine(double x, FAR struct hexfloat_s *hex, int prec,
+                       bool upper)
+{
+  hexfloat_dbits_t u;
+
+  memcpy(&u, &x, sizeof(u));
+  hexfloat_convert(hex, u >> (sizeof(u) * 8 - 1),
+                   (u >> (DBL_MANT_DIG - 1)) & (2 * DBL_MAX_EXP - 1),
+                   2 * DBL_MAX_EXP - 1,
+                   (uint64_t)u << (64 - DBL_MANT_DIG + 1), 0,
+                   (DBL_MANT_DIG + 2) / 4, prec, upper);
+}
+
+#ifdef HAVE_HEXFLOAT_LDOUBLE
+void __hexfloat_engine_ld(long double x, FAR struct hexfloat_s *hex,
+                          int prec, bool upper)
+{
+#  if LDBL_MANT_DIG == 113
+  uint64_t w[2];
+
+  memcpy(w, &x, sizeof(w));
+  hexfloat_convert(hex, w[HEXFLOAT_HI] >> 63,
+                   (w[HEXFLOAT_HI] >> 48) & 0x7fff, 0x7fff,
+                   (w[HEXFLOAT_HI] << 16) | (w[HEXFLOAT_LO] >> 48),
+                   w[HEXFLOAT_LO] << 16, 28, prec, upper);
+#  else
+  uint64_t mant;
+  uint16_t se;
+
+  /* The 80-bit format stores the bit before the point */
+
+  memcpy(&mant, &x, sizeof(mant));
+  memcpy(&se, (FAR const char *)&x + sizeof(mant), sizeof(se));
+  hexfloat_convert(hex, se >> 15, se & 0x7fff, 0x7fff, mant << 1, 0, 16,
+                   prec, upper);
+#  endif
+}
+#endif
diff --git a/libs/libc/stream/lib_hexfloat.h b/libs/libc/stream/lib_hexfloat.h
new file mode 100644
index 00000000000..970339e093a
--- /dev/null
+++ b/libs/libc/stream/lib_hexfloat.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+ * libs/libc/stream/lib_hexfloat.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __LIBS_LIBC_STREAM_LIB_HEXFLOAT_H
+#define __LIBS_LIBC_STREAM_LIB_HEXFLOAT_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/compiler.h>
+
+#include <float.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* A long double with more bits than a double has its own entry point.  The
+ * 64-bit mantissa format is the little-endian one of x86.
+ */
+
+#if defined(CONFIG_HAVE_LONG_DOUBLE) && \
+    (LDBL_MANT_DIG == 113 || \
+     (LDBL_MANT_DIG == 64 && !defined(CONFIG_ENDIAN_BIG)))
+#  define HAVE_HEXFLOAT_LDOUBLE
+#  define HEXFLOAT_MAX_DIG  ((LDBL_MANT_DIG + 2) / 4)
+#else
+#  define HEXFLOAT_MAX_DIG  ((DBL_MANT_DIG + 2) / 4)
+#endif
+
+#define HEXFLOAT_MINUS      1
+#define HEXFLOAT_INF        2
+#define HEXFLOAT_NAN        4
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+struct hexfloat_s
+{
+  int16_t exp;                        /* The exponent of two */
+  uint8_t flags;
+  uint8_t ndigs;                      /* The digits after the point */
+  char digits[HEXFLOAT_MAX_DIG + 1];  /* The first one is before the point */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/* A negative precision means as many digits as the value needs */
+
+void __hexfloat_engine(double x, FAR struct hexfloat_s *hex, int prec,
+                       bool upper);
+
+#ifdef HAVE_HEXFLOAT_LDOUBLE
+void __hexfloat_engine_ld(long double x, FAR struct hexfloat_s *hex,
+                          int prec, bool upper);
+#endif
+
+#endif /* __LIBS_LIBC_STREAM_LIB_HEXFLOAT_H */
diff --git a/libs/libc/stream/lib_libvsprintf.c 
b/libs/libc/stream/lib_libvsprintf.c
index 671f03f7e87..c65400f0199 100644
--- a/libs/libc/stream/lib_libvsprintf.c
+++ b/libs/libc/stream/lib_libvsprintf.c
@@ -54,6 +54,9 @@
 #include <sys/param.h>
 
 #include "lib_dtoa_engine.h"
+#ifdef CONFIG_LIBC_PRINTF_HEXFLOAT
+#  include "lib_hexfloat.h"
+#endif
 #include "lib_ultoa_invert.h"
 
 /****************************************************************************
@@ -218,6 +221,9 @@ union arg_u
   unsigned long long ull;
   double d;
   FAR char *cp;
+#ifdef HAVE_HEXFLOAT_LDOUBLE
+  long double ld;
+#endif
 };
 struct arg_s
 {
@@ -232,7 +238,7 @@ struct arg_s
  ****************************************************************************/
 
 static const char g_nullstring[] = "(null)";
-#ifdef CONFIG_LIBC_FLOATINGPOINT
+#if defined(CONFIG_LIBC_FLOATINGPOINT) || defined(CONFIG_LIBC_PRINTF_HEXFLOAT)
 static const char g_inf_lower[] = "inf";
 static const char g_nan_lower[] = "nan";
 static const char g_inf_upper[] = "INF";
@@ -375,6 +381,128 @@ static int vsprintf_putnum(FAR struct lib_outstream_s 
*stream,
   return len;
 }
 
+#ifdef CONFIG_LIBC_PRINTF_HEXFLOAT
+static size_t vsprintf_puthex(FAR struct lib_outstream_s *stream,
+                              FAR struct hexfloat_s *hex, uint16_t flags,
+                              int width, int prec, bool upper)
+{
+  size_t total_len = 0;
+  size_t flen = 3;
+  int nzero = 0;
+  int exp;
+  int nexp = 0;
+  bool finite = (hex->flags & (HEXFLOAT_INF | HEXFLOAT_NAN)) == 0;
+  char sign = 0;
+  char buf[6];
+
+  if ((hex->flags & HEXFLOAT_MINUS) != 0)
+    {
+      sign = '-';
+    }
+  else if ((flags & FL_PLUS) != 0)
+    {
+      sign = '+';
+    }
+  else if ((flags & FL_SPACE) != 0)
+    {
+      sign = ' ';
+    }
+
+  if (finite)
+    {
+      /* 0x1p+0: the digits of the exponent come below */
+
+      if (prec > hex->ndigs)
+        {
+          nzero = prec - hex->ndigs;
+        }
+
+      flen = 5u + (size_t)hex->ndigs + nzero;
+      if (hex->ndigs + nzero != 0 || (flags & FL_ALT_FORM) != 0)
+        {
+          flen++;
+        }
+
+      exp = hex->exp < 0 ? -hex->exp : hex->exp;
+      nexp = __ultoa_invert(exp, buf, 10) - buf;
+      flen += nexp;
+    }
+  else
+    {
+      flags &= ~FL_ZFILL;
+    }
+
+  if (sign != 0)
+    {
+      flen++;
+    }
+
+  if (flen > INT_MAX)
+    {
+      return INT_MAX + 1u;
+    }
+
+  width = width > flen ? width - flen : 0;
+
+  if ((flags & (FL_LPAD | FL_ZFILL)) == 0)
+    {
+      stream_repeat(' ', width, stream);
+      width = 0;
+    }
+
+  if (sign != 0)
+    {
+      stream_putc(sign, stream);
+    }
+
+  if (finite)
+    {
+      stream_putc('0', stream);
+      stream_putc(upper ? 'X' : 'x', stream);
+    }
+
+  if ((flags & FL_LPAD) == 0)
+    {
+      stream_repeat('0', width, stream);
+      width = 0;
+    }
+
+  if (finite)
+    {
+      stream_putc(hex->digits[0], stream);
+      if (hex->ndigs + nzero != 0 || (flags & FL_ALT_FORM) != 0)
+        {
+          stream_putc('.', stream);
+        }
+
+      if (hex->ndigs != 0)
+        {
+          stream_puts(&hex->digits[1], hex->ndigs, stream);
+        }
+
+      stream_repeat('0', nzero, stream);
+      stream_putc(upper ? 'P' : 'p', stream);
+      stream_putc(hex->exp < 0 ? '-' : '+', stream);
+      total_len += vsprintf_putnum(stream, buf, nexp);
+    }
+  else if ((hex->flags & HEXFLOAT_NAN) != 0)
+    {
+      stream_puts(upper ? g_nan_upper : g_nan_lower, 3, stream);
+    }
+  else
+    {
+      stream_puts(upper ? g_inf_upper : g_inf_lower, 3, stream);
+    }
+
+  if (FL_LPAD != 0 && width > 0)
+    {
+      stream_repeat(' ', width, stream);
+    }
+
+  return total_len;
+}
+#endif
+
 static int vsprintf_internal(FAR struct lib_outstream_s *stream,
                              FAR struct arg_s *arglist, int numargs,
                              FAR const IPTR char *fmt, va_list ap)
@@ -388,11 +516,15 @@ static int vsprintf_internal(FAR struct lib_outstream_s 
*stream,
     char __buf[22]; /* Size for -1 in octal, without '\0' */
 #ifdef CONFIG_LIBC_FLOATINGPOINT
     struct dtoa_s __dtoa;
+#endif
+#ifdef CONFIG_LIBC_PRINTF_HEXFLOAT
+    struct hexfloat_s __hex;
 #endif
   } u;
 
 #define buf     (u.__buf)
 #define _dtoa   (u.__dtoa)
+#define _hex    (u.__hex)
 
   FAR const char *pnt;
   size_t size;
@@ -829,6 +961,54 @@ set_star:
         }
 #endif
 
+#ifdef CONFIG_LIBC_PRINTF_HEXFLOAT
+      if ((c | 0x20) == 'a')
+        {
+          /* A negative precision means none */
+
+          star = (flags & FL_PREC) != 0 ? prec : -1;
+
+#  ifdef CONFIG_LIBC_NUMBERED_ARGS
+          if ((flags & FL_ARGNUMBER) != 0)
+            {
+#    ifdef HAVE_HEXFLOAT_LDOUBLE
+              if (FL_IS_LDOUBLE(flags))
+                {
+                  __hexfloat_engine_ld(arglist->value[argnumber - 1].ld,
+                                       &_hex, star, c == 'A');
+                }
+              else
+#    endif
+                {
+                  __hexfloat_engine(arglist->value[argnumber - 1].d,
+                                    &_hex, star, c == 'A');
+                }
+            }
+          else
+#  endif
+#  ifdef CONFIG_HAVE_LONG_DOUBLE
+          if (FL_IS_LDOUBLE(flags))
+            {
+#    ifdef HAVE_HEXFLOAT_LDOUBLE
+              __hexfloat_engine_ld(va_arg(ap, long double), &_hex, star,
+                                   c == 'A');
+#    else
+              __hexfloat_engine(vsprintf_ldtod(va_arg(ap, long double)),
+                                &_hex, star, c == 'A');
+#    endif
+            }
+          else
+#  endif
+            {
+              __hexfloat_engine(va_arg(ap, double), &_hex, star, c == 'A');
+            }
+
+          total_len += vsprintf_puthex(stream, &_hex, flags, width, star,
+                                       c == 'A');
+          continue;
+        }
+
+#endif
 #ifdef CONFIG_LIBC_FLOATINGPOINT
       if ((c >= 'E' && c <= 'G') || c == 'A')
         {
@@ -883,7 +1063,16 @@ flt_oper:
 #ifdef CONFIG_LIBC_NUMBERED_ARGS
           if ((flags & FL_ARGNUMBER) != 0)
             {
-              value = arglist->value[argnumber - 1].d;
+#  ifdef HAVE_HEXFLOAT_LDOUBLE
+              if (FL_IS_LDOUBLE(flags))
+                {
+                  value = vsprintf_ldtod(arglist->value[argnumber - 1].ld);
+                }
+              else
+#  endif
+                {
+                  value = arglist->value[argnumber - 1].d;
+                }
             }
           else
 #endif
@@ -1819,7 +2008,10 @@ int lib_vsprintf(FAR struct lib_outstream_s *stream,
 
 #ifdef CONFIG_HAVE_LONG_DOUBLE
           case TYPE_LONG_DOUBLE:
-#  ifdef CONFIG_LIBC_FLOATINGPOINT
+#  ifdef HAVE_HEXFLOAT_LDOUBLE
+            arglist.value[i].ld = va_arg(ap, long double);
+#  elif defined(CONFIG_LIBC_FLOATINGPOINT) || \
+        defined(CONFIG_LIBC_PRINTF_HEXFLOAT)
             arglist.value[i].d = vsprintf_ldtod(va_arg(ap, long double));
 #  else
             va_arg(ap, long double);

Reply via email to