Dear all,

the conversion of (unsigned) integers to decimal in output was designed
to be efficient up to INTEGER(kind=16) and did not handle values larger
than roughly (10^19 * 2^64).

The attached obvious patch fixes this.

Regtested on x86_64-pc-linux-gnu.  OK for mainline?

Thanks,
Harald

From f66049d52327242743e7e9ff59d8373fcb333212 Mon Sep 17 00:00:00 2001
From: Harald Anlauf <anl...@gmx.de>
Date: Thu, 16 Jan 2025 20:23:06 +0100
Subject: [PATCH] libfortran: fix conversion of UNSIGNED(kind=16) to decimal in
 output [PR118406]

	PR libfortran/118406

libgfortran/ChangeLog:

	* runtime/string.c (gfc_itoa): Handle unsigned integers larger than
	(10^19 * 2^64).

gcc/testsuite/ChangeLog:

	* gfortran.dg/unsigned_write.f90: New test.
---
 gcc/testsuite/gfortran.dg/unsigned_write.f90 | 40 ++++++++++++++++++++
 libgfortran/runtime/string.c                 | 35 ++++++++++++-----
 2 files changed, 66 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/gfortran.dg/unsigned_write.f90

diff --git a/gcc/testsuite/gfortran.dg/unsigned_write.f90 b/gcc/testsuite/gfortran.dg/unsigned_write.f90
new file mode 100644
index 00000000000..903c212ee3a
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/unsigned_write.f90
@@ -0,0 +1,40 @@
+! { dg-do  run }
+! This is a libgfortran (runtime library) test, need to run only once!
+!
+! { dg-require-effective-target fortran_integer_16 }
+! { dg-additional-options "-funsigned" }
+!
+! PR libfortran/118406 - printing large UNSIGNED(kind=16) crashes
+
+program print_large_unsigned
+  unsigned(16), parameter :: u16_max =      huge(0U_16)
+  unsigned(16), parameter :: u8_max  = uint(huge(0U_8),16)        ! UINT64_MAX
+  unsigned(16), parameter :: ten19   = uint(10_8 ** 18,16)*10U_16 ! 10**19
+  character(42) :: s
+
+  ! Reference: signed integer
+  write(s,*)       huge(0_16)
+  if (adjustl (s) /= "170141183460469231731687303715884105727") stop 1
+
+  ! Same value as unsigned
+  write(s,*) uint (huge(0_16),16)
+  if (adjustl (s) /= "170141183460469231731687303715884105727") stop 2
+
+  ! Extreme and intermediate values
+  write(s,*) u16_max
+  if (adjustl (s) /= "340282366920938463463374607431768211455") stop 3
+
+  write(s,*) (u16_max - 3U_16) / 4U_16 * 3U_16
+  if (adjustl (s) /= "255211775190703847597530955573826158589") stop 4
+
+  ! Test branches of implementation in string.c::gfc_itoa
+  write(s,*) u8_max * ten19
+  if (adjustl (s) /= "184467440737095516150000000000000000000") stop 5
+
+  write(s,*) u8_max * ten19 - 1U_16
+  if (adjustl (s) /= "184467440737095516149999999999999999999") stop 6
+
+  write(s,*) u8_max * ten19 + 1U_16
+  if (adjustl (s) /= "184467440737095516150000000000000000001") stop 7
+
+end
diff --git a/libgfortran/runtime/string.c b/libgfortran/runtime/string.c
index 8acc94292c4..a0e2a85e8e2 100644
--- a/libgfortran/runtime/string.c
+++ b/libgfortran/runtime/string.c
@@ -241,18 +241,35 @@ gfc_itoa (GFC_UINTEGER_LARGEST n, char *buffer, size_t len)
 	 the uint64_t function are not sufficient for all 128-bit unsigned
 	 integers (we would need three calls), but they do suffice for all
 	 values up to 2^127, which is the largest that Fortran can produce
-	 (-HUGE(0_16)-1) with its signed integer types.  */
+	 (-HUGE(0_16)-1) with its signed integer types.
+	 With the introduction of UNSIGNED integers, we must treat the case
+	 of unsigned ints larger than (10^19 * 2^64) by adding one step.  */
       _Static_assert (sizeof(GFC_UINTEGER_LARGEST) <= 2 * sizeof(uint64_t),
 		      "integer too large");
 
-      GFC_UINTEGER_LARGEST r;
-      r = n % TEN19;
-      n = n / TEN19;
-      assert (r <= UINT64_MAX);
-      p = itoa64_pad19 (r, p);
-
-      assert(n <= UINT64_MAX);
-      return itoa64 (n, p);
+      if (n <= TEN19 * UINT64_MAX)
+	{
+	  GFC_UINTEGER_LARGEST r;
+	  r = n % TEN19;
+	  n = n / TEN19;
+	  assert (r <= UINT64_MAX);
+	  p = itoa64_pad19 (r, p);
+
+	  assert(n <= UINT64_MAX);
+	  return itoa64 (n, p);
+	}
+      else
+	{
+	  /* Here n > (10^19 * 2^64).  */
+	  GFC_UINTEGER_LARGEST d1, r1, d2, r2;
+	  d1 = n / (TEN19 * TEN19);
+	  r1 = n % (TEN19 * TEN19);
+	  d2 = r1 / TEN19;
+	  r2 = r1 % TEN19;
+	  p = itoa64_pad19 (r2, p);
+	  p = itoa64_pad19 (d2, p);
+	  return itoa64 (d1, p);
+	}
     }
 #else
   /* On targets where the largest integer is 64-bit, just use that.  */
-- 
2.43.0

Reply via email to