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