stdlib/strfrom: Add copysign to fix NAN issue on riscv (BZ #29501)

Message ID 20221005111353.3921-1-fantasquex@gmail.com
State Superseded
Headers
Series stdlib/strfrom: Add copysign to fix NAN issue on riscv (BZ #29501) |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent
dj/TryBot-32bit success Build for i686

Commit Message

Letu Ren Oct. 5, 2022, 11:13 a.m. UTC
  According to the specification of ISO/IEC TS 18661-1:2014,

The strfromd, strfromf, and strfroml functions are equivalent to
snprintf(s, n, format, fp) (7.21.6.5), except the format string contains only
the character %, an optional precision that does not contain an asterisk *, and
one of the conversion specifiers a, A, e, E, f, F, g, or G, which applies to
the type (double, float, or long double) indicated by the function suffix
(rather than  by a length modifier). Use of these functions with any other 20
format string results in undefined behavior.

strfromf will convert the arguement with type float to double first.

According to the latest version of IEEE754 which is published in 2019,

Conversion of a quiet NaN from a narrower format to a wider format in the same
radix, and then back to the same narrower format, should not change the quiet
NaN payload in any way except to make it canonical.

When either an input or result is a NaN, this standard does not interpret the
sign of a NaN. However, operations on bit strings—copy, negate, abs,
copySign—specify the sign bit of a NaN result, sometimes based upon the sign
bit of a NaN operand. The logical predicates totalOrder and isSignMinus are
also affected by the sign bit of a NaN operand. For all other operations, this
standard does not specify the sign bit of a NaN result, even when there is only
one input NaN, or when the NaN is produced from an invalid operation.

converting NAN or -NAN with type float to double doesn't need to keep
the signbit. As a result, this test case isn't mandatory.

The problem is that according to RISC-V ISA manual in chapter 11.3 of
riscv-isa-20191213,

Except when otherwise stated, if the result of a floating-point operation is
NaN, it is the canonical NaN. The canonical NaN has a positive sign and all
significand bits clear except the MSB, a.k.a. the quiet bit. For
single-precision floating-point, this corresponds to the pattern 0x7fc00000.

which means that conversion -NAN from float to double won't keep the signbit.

Since glibc ought to be consistent here between types and architectures, this
patch adds copysign to fix this problem if the string is NAN. This patch
leverge Macro instead of creating a function under sysdeps directory
because there is only one place which needs a keep-sign conversion.

This patch has been tested on x86_64 and riscv64.

Resolves: BZ #29501

Signed-off-by: Letu Ren <fantasquex@gmail.com>
---
 stdlib/strfrom-skeleton.c | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)
  

Comments

Joseph Myers Oct. 5, 2022, 4 p.m. UTC | #1
On Wed, 5 Oct 2022, Letu Ren via Libc-alpha wrote:

> Since glibc ought to be consistent here between types and architectures, this
> patch adds copysign to fix this problem if the string is NAN. This patch
> leverge Macro instead of creating a function under sysdeps directory
> because there is only one place which needs a keep-sign conversion.

We avoid architecture-specific conditionals in architecture-independent 
code as far as possible.  The way to handle this kind of thing is a 
sysdeps header containing an inline function to convert from float to 
double while preserving NaN signs.
  

Patch

diff --git a/stdlib/strfrom-skeleton.c b/stdlib/strfrom-skeleton.c
index 1fba04bf6a..f22b8bb30b 100644
--- a/stdlib/strfrom-skeleton.c
+++ b/stdlib/strfrom-skeleton.c
@@ -33,6 +33,10 @@ 
 #define ISDIGIT(Ch) isdigit (Ch)
 #include "stdio-common/printf-parse.h"
 
+#ifdef __riscv
+#include <math.h>
+#endif
+
 int
 STRFROM (char *dest, size_t size, const char *format, FLOAT f)
 {
@@ -61,7 +65,19 @@  STRFROM (char *dest, size_t size, const char *format, FLOAT f)
      because __printf_fp and __printf_fphex only accept double and long double
      as the floating-point argument.  */
   if (__builtin_types_compatible_p (FLOAT, float))
-    fpnum.flt = f;
+  {
+#ifdef __riscv
+    if (isnan(f))
+    {
+      float x = copysignf(1.f, f);
+      fpnum.flt = copysign((double) f, (double) x);
+    }
+    else
+      fpnum.flt = (double) f;
+#else
+    fpnum.flt = (double) f;
+#endif
+  }
   else
     fpnum.value = f;