Hi all,
This took me a lot longer than I wanted, but finally got it done.
See the attached patch. Since my last submittal here I started over on using the
example demo Harald gave in Comment 18 of the PR using available higher level C
functions, fabs, signbit, scalbn, isnan frexp, etc to extract the needed
components. Then, using the %H specifier to build the hexadecimal float string
in get_float_hex_string(). I added checks for zero, inf, nan, and the sign.
Once I had this working for kind=8, I repeated the pattern for kind=8, 10, and
16. I used defines around the kind=10 and 16 to avoid where not supported.
I also created defines for the C functions needed following the examples later
in the file. I dont have some of those platforms readily available here so a web
searched for those various function, ie not tested yet. I will call it an EWAG.
Engineering Wild Assumption and Guess.
I apologize for the whitespace cleanups. I had them in my worktree from earlier
attempts.
I updated the two test cases added. I probably need to specify targets on those.
We can add additional testcases as deemed appropriate.
Regression tested here on X86_64. Testing by others encouraged.
OK for mainline. I do not plan any backport and less others think its worth it.
Best regards,
Jerry
PS I will be on travel the next 7 day however I can monitor email.
From 28afb48d5292268f542f23fdc1096797157e74d9 Mon Sep 17 00:00:00 2001
From: Jerry DeLisle <[email protected]>
Date: Sun, 10 May 2026 18:13:48 -0700
Subject: [PATCH] Fortran: [PR93727] Implement EX format specifier for WRITE
These changes implement the Fortran 2018 EX format specifier
for WRITE output. READ will be a later change. This implements
hexadecimal floating point formats for KIND=4,8,10, and 16 real
numbers if supported by the configured machine.
Format tokens are added as place holders for future leading
zero specifiers.
PR fortran/93727
gcc/fortran/ChangeLog:
* io.cc (enum format_token): Add FMT_EX, FMT_LPS, FMT_LPZ, FMT_LZ
enums to identify specific tokens.
(format_lex): Add parsing and checking of the EXw.d and EXw.dEe edit
specifiers.
libgfortran/ChangeLog:
* io/format.c (format_lex): Add new FMT_EX token handing.
(parse_format_list): Likewise.
(next_format): Likewise
* io/io.h (write_ex): Add prototype for new function.
(internal_proto): Likewise.
* io/transfer.c (formatted_transfer_scalar_write): Use FMT_EX token.
* io/write.c (write_default_char4): White space fix.
(write_a): White space fix.
(write_boz): White space fix.
(write_decimal): White space fix.
(otoa_big): White space fix.
(write_character): White space fix.
(write_float_0): White space fix.
(write_ex): New function which uses the new helper function
get_float_hex_string() to build the hexadecimal float format for
output.
(write_real): White space fix.
(write_complex): White space fix.
(nml_write_obj): White space fix.
(namelist_write): White space fix.
* io/write_float.def: Add defines to handle the various forms of
KIND=16 floats. These handle the selection of the appropriate versions
of the frexp, fabs, and scalbn used to extract the components of the
floating point values.
(GFC_REAL_16_FREXP): New define.
(GFC_REAL_16_FABS): New define.
(GFC_REAL_16_SCALBN): New define.
(get_float_hex_string): New function which exatracts the bits and builds
the basic hexadecimal format strings into a buffer. The buffer is provided
by the caller write_ex.
(build_float_string): White space fix.
(quadmath_snprintf): White space fix.
(determine_en_precision): White space fix.
gcc/testsuite/ChangeLog:
* gfortran.dg/EXformat_1.f90: New test.
* gfortran.dg/EXformat_2.f90: New test.
---
gcc/fortran/io.cc | 45 +++-
gcc/testsuite/gfortran.dg/EXformat_1.f90 | 30 +++
gcc/testsuite/gfortran.dg/EXformat_2.f90 | 24 ++
libgfortran/io/format.c | 11 +-
libgfortran/io/io.h | 6 +-
libgfortran/io/transfer.c | 11 +-
libgfortran/io/write.c | 245 ++++++++++++++++--
libgfortran/io/write_float.def | 311 ++++++++++++++++++++++-
8 files changed, 635 insertions(+), 48 deletions(-)
create mode 100644 gcc/testsuite/gfortran.dg/EXformat_1.f90
create mode 100644 gcc/testsuite/gfortran.dg/EXformat_2.f90
diff --git a/gcc/fortran/io.cc b/gcc/fortran/io.cc
index 0a81d4a168a..16d54871611 100644
--- a/gcc/fortran/io.cc
+++ b/gcc/fortran/io.cc
@@ -118,11 +118,11 @@ static gfc_dt *current_dt;
enum format_token
{
FMT_NONE, FMT_UNKNOWN, FMT_SIGNED_INT, FMT_ZERO, FMT_POSINT, FMT_PERIOD,
- FMT_COMMA, FMT_COLON, FMT_SLASH, FMT_DOLLAR, FMT_LPAREN,
- FMT_RPAREN, FMT_X, FMT_SIGN, FMT_BLANK, FMT_CHAR, FMT_P, FMT_IBOZ, FMT_F,
- FMT_E, FMT_EN, FMT_ES, FMT_G, FMT_L, FMT_A, FMT_D, FMT_H, FMT_END,
- FMT_ERROR, FMT_DC, FMT_DP, FMT_T, FMT_TR, FMT_TL, FMT_STAR, FMT_RC,
- FMT_RD, FMT_RN, FMT_RP, FMT_RU, FMT_RZ, FMT_DT
+ FMT_COMMA, FMT_COLON, FMT_SLASH, FMT_DOLLAR, FMT_LPAREN, FMT_RPAREN, FMT_X,
+ FMT_SIGN, FMT_BLANK, FMT_CHAR, FMT_P, FMT_IBOZ, FMT_F, FMT_E, FMT_EN, FMT_ES,
+ FMT_G, FMT_L, FMT_A, FMT_D, FMT_H, FMT_END, FMT_ERROR, FMT_DC, FMT_DP, FMT_T,
+ FMT_TR, FMT_TL, FMT_STAR, FMT_RC, FMT_RD, FMT_RN, FMT_RP, FMT_RU, FMT_RZ,
+ FMT_DT, FMT_EX, FMT_LPS, FMT_LPZ, FMT_LZ
};
/* Local variables for checking format strings. The saved_token is
@@ -422,6 +422,8 @@ format_lex (void)
token = FMT_EN;
else if (c == 'S')
token = FMT_ES;
+ else if (c == 'X')
+ token = FMT_EX;
else
{
token = FMT_E;
@@ -439,6 +441,37 @@ format_lex (void)
break;
case 'L':
+ c = next_char_not_space ();
+ switch (c)
+ {
+ case 'P':
+ c = next_char_not_space ();
+ switch (c)
+ {
+ case 'S':
+ token = FMT_LPS;
+ break;
+
+ case 'Z':
+ token = FMT_LPZ;
+ break;
+
+ default:
+ token = FMT_UNKNOWN;
+ unget_char ();
+ break;
+ }
+ break;
+
+ case 'Z':
+ token = FMT_LZ;
+ break;
+
+ default:
+ token = FMT_UNKNOWN;
+ unget_char ();
+ break;
+ }
token = FMT_L;
break;
@@ -746,6 +779,7 @@ format_item_1:
case FMT_E:
case FMT_EN:
case FMT_ES:
+ case FMT_EX:
case FMT_G:
case FMT_L:
case FMT_A:
@@ -879,6 +913,7 @@ data_desc:
case FMT_D:
case FMT_E:
+ case FMT_EX:
case FMT_G:
case FMT_EN:
case FMT_ES:
diff --git a/gcc/testsuite/gfortran.dg/EXformat_1.f90 b/gcc/testsuite/gfortran.dg/EXformat_1.f90
new file mode 100644
index 00000000000..1d95ca7b385
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/EXformat_1.f90
@@ -0,0 +1,30 @@
+! { dg-do run }
+! pr93727 EX Format Specifiers, testing various kinds, default field widths
+program main
+ implicit none
+ character(kind=1, len=48) :: s1
+ real(4) :: r4
+ real(8) :: r8
+ real(10) :: r10
+ real(16) :: r16
+ r4 = -huge(1.0_4)
+ r8 = -huge( 1.0_8)
+ r10= -huge(1.0_10)
+ r16 = 1.0_16/3.0_16
+
+ write(s1,"(EX0.0,'<')") r4
+ if (s1.ne."-0XF.FFFFFP+124<") stop 1
+ write(s1,"(EX0.0,'<')") r8
+ if (s1.ne."-0XF.FFFFFFFFFFFF8P+1020<") stop 2
+ write(s1,"(EX0.0,'<')") r10
+ if (s1.ne."-0XF.FFFFFFFFFFFFFFFP+16380<") stop 3
+
+ write(s1,"(EX0.0,'<')") 1.0_4/r4
+ if (s1.ne."-0X8.P-131<") stop 4
+ write(s1,"(EX0.0,'<')") 1.0_8/r8
+ if (s1.ne."-0X8.P-1027<") stop 5
+ write(s1,"(EX0.0,'<')") 1.0_10/r10
+ if (s1.ne."-0X8.P-16387<") stop 6
+ write(s1,"(EX0.0,'<')") r16
+ if (s1.ne."0XA.AAAAAAAAAAAAAAAAAAAAAAAAAAA8P-5<") stop 7
+end program main
diff --git a/gcc/testsuite/gfortran.dg/EXformat_2.f90 b/gcc/testsuite/gfortran.dg/EXformat_2.f90
new file mode 100644
index 00000000000..69f7482a6a5
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/EXformat_2.f90
@@ -0,0 +1,24 @@
+! { dg-do run }
+! PR93727 Test writing EX as character(kind=1) output
+program kind1
+ implicit none
+ integer, parameter :: wp = 8
+ real(kind=wp) :: num
+ character(kind=1,len=45) :: str1
+
+ num = -3.14159682678_wp * 25._wp
+ write(str1, '(">",EX30.0,"<")') num
+ if (str1.ne."> -0X9.D14707B63DFBP+3<") stop 1
+ write(str1, '(">",EX30.1,"<")') num
+ if (str1.ne."> -0X9.DP+3<") stop 2
+ write(str1, '(">",EX30.2,"<")') num
+ if (str1.ne."> -0X9.D1P+3<") stop 3
+ write(str1, '(">",EX30.3,"<")') num
+ if (str1.ne."> -0X9.D14P+3<") stop 4
+ write(str1, '(">",EX30.4,"<")') num
+ if (str1.ne."> -0X9.D147P+3<") stop 5
+ write(str1, '(">",EX30.15e8,"<")') num
+ if (str1.ne.">-0X9.D14707B63DFB000P+00000003<") stop 6
+ write(str1, '(">",EX8.5,"<")') num
+ if (str1.ne.">********<") stop 7
+end program kind1
diff --git a/libgfortran/io/format.c b/libgfortran/io/format.c
index 899a0b50f95..cff94a63f79 100644
--- a/libgfortran/io/format.c
+++ b/libgfortran/io/format.c
@@ -522,6 +522,9 @@ format_lex (format_data *fmt)
case 'S':
token = FMT_ES;
break;
+ case 'X':
+ token = FMT_EX;
+ break;
default:
token = FMT_E;
unget_char (fmt);
@@ -706,7 +709,7 @@ parse_format_list (st_parameter_dt *dtp, bool *seen_dd)
tail->repeat = 1;
t = format_lex (fmt);
- if (t == FMT_F || t == FMT_EN || t == FMT_ES || t == FMT_D
+ if (t == FMT_F || t == FMT_EN || t == FMT_ES || t == FMT_EX || t == FMT_D
|| t == FMT_G || t == FMT_E)
{
repeat = 1;
@@ -818,6 +821,7 @@ parse_format_list (st_parameter_dt *dtp, bool *seen_dd)
case FMT_E:
case FMT_EN:
case FMT_ES:
+ case FMT_EX:
case FMT_D:
case FMT_DT:
case FMT_L:
@@ -921,6 +925,7 @@ parse_format_list (st_parameter_dt *dtp, bool *seen_dd)
case FMT_G:
case FMT_EN:
case FMT_ES:
+ case FMT_EX:
*seen_dd = true;
get_fnode (fmt, &head, &tail, t);
tail->repeat = repeat;
@@ -1538,8 +1543,8 @@ next_format (st_parameter_dt *dtp)
if (!fmt->reversion_ok &&
(t == FMT_I || t == FMT_B || t == FMT_O || t == FMT_Z || t == FMT_F ||
- t == FMT_E || t == FMT_EN || t == FMT_ES || t == FMT_G || t == FMT_L ||
- t == FMT_A || t == FMT_D || t == FMT_DT))
+ t == FMT_E || t == FMT_EN || t == FMT_ES || t== FMT_EX || t == FMT_G ||
+ t == FMT_L || t == FMT_A || t == FMT_D || t == FMT_DT))
fmt->reversion_ok = 1;
return f;
}
diff --git a/libgfortran/io/io.h b/libgfortran/io/io.h
index 7928c196f63..1fd0908859f 100644
--- a/libgfortran/io/io.h
+++ b/libgfortran/io/io.h
@@ -145,7 +145,8 @@ typedef enum
FMT_LPAREN, FMT_RPAREN, FMT_X, FMT_S, FMT_SS, FMT_SP, FMT_STRING,
FMT_BADSTRING, FMT_P, FMT_I, FMT_B, FMT_BN, FMT_BZ, FMT_O, FMT_Z, FMT_F,
FMT_E, FMT_EN, FMT_ES, FMT_G, FMT_L, FMT_A, FMT_D, FMT_H, FMT_END, FMT_DC,
- FMT_DP, FMT_STAR, FMT_RC, FMT_RD, FMT_RN, FMT_RP, FMT_RU, FMT_RZ, FMT_DT
+ FMT_DP, FMT_STAR, FMT_RC, FMT_RD, FMT_RN, FMT_RP, FMT_RU, FMT_RZ, FMT_DT, FMT_EX,
+ FMT_LPS, FMT_LPZ, FMT_LZ
}
format_token;
@@ -946,6 +947,9 @@ internal_proto(write_en);
extern void write_es (st_parameter_dt *, const fnode *, const char *, int);
internal_proto(write_es);
+extern void write_ex (st_parameter_dt *, const fnode *, const char *, int);
+internal_proto(write_ex);
+
extern void write_f (st_parameter_dt *, const fnode *, const char *, int);
internal_proto(write_f);
diff --git a/libgfortran/io/transfer.c b/libgfortran/io/transfer.c
index c81993e4635..99e90f3c803 100644
--- a/libgfortran/io/transfer.c
+++ b/libgfortran/io/transfer.c
@@ -2129,7 +2129,7 @@ formatted_transfer_scalar_write (st_parameter_dt *dtp, bt type, void *p, int kin
|| t == FMT_Z || t == FMT_F || t == FMT_E
|| t == FMT_EN || t == FMT_ES || t == FMT_G
|| t == FMT_L || t == FMT_A || t == FMT_D
- || t == FMT_DT))
+ || t == FMT_DT || t == FMT_EX))
|| t == FMT_STRING))
{
if (dtp->u.p.skips > 0)
@@ -2351,6 +2351,15 @@ formatted_transfer_scalar_write (st_parameter_dt *dtp, bt type, void *p, int kin
write_es (dtp, f, p, kind);
break;
+ case FMT_EX:
+ if (n == 0)
+ goto need_data;
+ if (require_type (dtp, BT_REAL, type, f))
+ return;
+ write_ex (dtp, f, p, kind);
+ break;
+
+
case FMT_F:
if (n == 0)
goto need_data;
diff --git a/libgfortran/io/write.c b/libgfortran/io/write.c
index a5e89c64951..44f4b614c4f 100644
--- a/libgfortran/io/write.c
+++ b/libgfortran/io/write.c
@@ -30,6 +30,7 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#include "unix.h"
#include <assert.h>
#include <string.h>
+#include "config.h"
#define star_fill(p, n) memset(p, '*', n)
@@ -127,7 +128,7 @@ write_default_char4 (st_parameter_dt *dtp, const gfc_char4_t *source,
return;
*p++ = (uchar) c;
}
- else
+ else
{
p = write_block (dtp, 1);
if (p == NULL)
@@ -409,8 +410,8 @@ write_a (st_parameter_dt *dtp, const fnode *f, const char *source, size_t len)
/* Write out the CR_LF sequence. */
q++;
p = write_block (dtp, 2);
- if (p == NULL)
- return;
+ if (p == NULL)
+ return;
memcpy (p, crlf, 2);
}
else
@@ -709,11 +710,11 @@ write_boz (st_parameter_dt *dtp, const fnode *f, const char *q, int n, int len)
if (m == 0 && n == 0)
{
if (w == 0)
- w = 1;
+ w = 1;
p = write_block (dtp, w);
if (p == NULL)
- return;
+ return;
if (unlikely (is_char4_unit (dtp)))
{
gfc_char4_t *p4 = (gfc_char4_t *) p;
@@ -825,11 +826,11 @@ write_decimal (st_parameter_dt *dtp, const fnode *f, const char *source,
if (m == 0 && n == 0)
{
if (w == 0)
- w = 1;
+ w = 1;
p = write_block (dtp, w);
if (p == NULL)
- return;
+ return;
if (unlikely (is_char4_unit (dtp)))
{
gfc_char4_t *p4 = (gfc_char4_t *) p;
@@ -1250,7 +1251,7 @@ otoa_big (const char *s, char *buffer, int len, GFC_UINTEGER_LARGEST *n)
octet |= (c & 1) << j;
c >>= 1;
if (++k > 7)
- {
+ {
i++;
k = 0;
c = *--p;
@@ -1275,7 +1276,7 @@ otoa_big (const char *s, char *buffer, int len, GFC_UINTEGER_LARGEST *n)
octet |= (c & 1) << j;
c >>= 1;
if (++k > 7)
- {
+ {
i++;
k = 0;
c = *++p;
@@ -1661,9 +1662,9 @@ write_character (st_parameter_dt *dtp, const char *source, int kind, size_t leng
*p++ = d;
for (size_t i = 0; i < length; i++)
- {
- *p++ = source[i];
- if (source[i] == d)
+ {
+ *p++ = source[i];
+ if (source[i] == d)
*p++ = d;
}
@@ -1812,7 +1813,7 @@ write_float_0 (st_parameter_dt *dtp, const fnode *f, const char *source, int kin
buffer = select_buffer (dtp, f, precision, buf_stack, &buf_size, kind);
get_float_string (dtp, f, source , kind, 0, buffer,
- precision, buf_size, result, &flt_str_len);
+ precision, buf_size, result, &flt_str_len);
write_float_string (dtp, result, flt_str_len);
if (buf_size > BUF_STACK_SZ)
@@ -1855,6 +1856,196 @@ write_es (st_parameter_dt *dtp, const fnode *f, const char *p, int len)
write_float_0 (dtp, f, p, len);
}
+void
+write_ex (st_parameter_dt *dtp, const fnode *f, const char *p, int kind)
+{
+ /* The EX specifier in Fortran 2018 produces hexadecimal floating-point
+ output. The format is EXw.dEe where:
+ - w is the total field width
+ - d is the number of significant hex digits after the radix point
+ - e is the width of the exponent field (including 'p' and sign)
+
+ Example output: 0x1.23p+10 or -0x1.abcp-5 */
+
+ char buf[64];
+ char output[64];
+ char *p_pos, *exp_pos, *decimal;
+ char sign_char;
+ int w, d, e, result, res_len;
+ int exp_value;
+ int mantissa_digits;
+ size_t output_len, mantissa_len, copy_len;
+
+ /* Get the user supplied width parameters. */
+
+ w = f->u.real.w; /* Total field width */
+ d = f->u.real.d; /* Significant hex digits after decimal */
+ e = f->u.real.e == -1 ? 0 : f->u.real.e; /* Exponent field width */
+
+ /* Get the hex float string using uppercase format (e.g., 0X1.23P+10) */
+ result = get_float_hex_string (p, kind, buf, &res_len);
+
+ if (result < 0)
+ {
+ /* Error - output asterisks */
+ w = (w > 0) ? w : 1;
+ char *out = write_block (dtp, w);
+ if (out != NULL)
+ memset (out, '*', w);
+ return;
+ }
+
+ /* Find the exponent marker 'P' (uppercase from %A format) */
+ p_pos = strchr (buf, 'P');
+ if (p_pos == NULL)
+ {
+ /* No exponent found - this occurs when the value is INF or NAN */
+ strncpy (output, buf, sizeof (output) - 1);
+ output[sizeof (output) - 1] = '\0';
+ output_len = strlen (output);
+ goto write_output;
+ }
+
+ /* Parse exponent value */
+ exp_pos = p_pos + 1;
+ sign_char = '+';
+ if (*exp_pos == '+' || *exp_pos == '-')
+ {
+ sign_char = *exp_pos;
+ exp_pos++;
+ }
+
+ if (sscanf (exp_pos, "%d", &exp_value) != 1)
+ {
+ /* Failed to parse - use original */
+ strncpy (output, buf, sizeof (output) - 1);
+ output[sizeof (output) - 1] = '\0';
+ output_len = strlen (output);
+ goto write_output;
+ }
+
+ /* Handle the 'd' parameter - trim trailing zeros before 'P'. */
+ if (d == 0)
+ {
+ decimal = strchr (buf, '.');
+ if (decimal != NULL && decimal < p_pos)
+ {
+ char *trim = p_pos - 1;
+ while (trim > decimal && *trim == '0')
+ trim--;
+ /* Shift 'P...' part left to just after last non-zero digit. */
+ if (trim + 1 < p_pos)
+ {
+ memmove (trim + 1, p_pos, strlen (p_pos) + 1);
+ p_pos = trim + 1;
+ }
+ }
+ }
+
+ /* Handle the 'd' parameter - adjust mantissa precision if specified */
+ if (d > 0)
+ {
+ /* Find the decimal point in mantissa */
+ decimal = strchr (buf, '.');
+ if (decimal != NULL && decimal < p_pos)
+ {
+ /* Count current mantissa digits after decimal point */
+ mantissa_digits = p_pos - decimal - 1;
+
+ /* Adjust mantissa to have exactly 'd' digits after decimal */
+ if (d < mantissa_digits)
+ {
+ /* Truncate mantissa */
+ memmove (decimal + d + 1, p_pos, strlen (p_pos) + 1);
+ p_pos = decimal + d + 1;
+ }
+ else if (d > mantissa_digits)
+ {
+ /* Pad with zeros - shift exponent part right */
+ int pad_count = d - mantissa_digits;
+ if (strlen (buf) + pad_count < sizeof (buf))
+ {
+ memmove (p_pos + pad_count, p_pos, strlen (p_pos) + 1);
+ memset (p_pos, '0', pad_count);
+ p_pos += pad_count;
+ }
+ }
+ }
+ }
+
+ /* Format the exponent field with specified width 'e'. The 'e' parameter
+ is the total exponent width INCLUDING 'P' and the sign. */
+
+ int exp_digits = e;
+ if (exp_digits < 1)
+ exp_digits = 1; /* Minimum 1 digit */
+
+ /* Construct output with formatted exponent */
+ mantissa_len = p_pos - buf;
+ if (mantissa_len >= sizeof (output))
+ mantissa_len = sizeof (output) - 1;
+
+ memcpy (output, buf, mantissa_len);
+ snprintf (output + mantissa_len, sizeof (output) - mantissa_len,
+ "P%c%0*d", sign_char, exp_digits, abs (exp_value));
+
+ output_len = strlen (output);
+
+ /* Check the field width 'w' if specified. If the field width is not
+ wide enough, fill it with "*" before writing it out. */
+ if (w > 0 && (output_len > (size_t) w))
+ {
+ char *out = write_block (dtp, w);
+ if (out != NULL)
+ {
+ if (unlikely (is_char4_unit (dtp)))
+ {
+ gfc_char4_t *out4 = (gfc_char4_t *) out;
+ memset4 (out4, '*', w);
+ }
+ else
+ memset (out, '*', w);
+ }
+ return;
+ }
+
+write_output:
+
+ /* Determine actual output width */
+ int actual_width = (w > 0) ? w : (int) output_len;
+
+ /* Get the block of memory that will be transferred out. */
+ char *out = write_block (dtp, actual_width);
+ if (out == NULL)
+ return;
+
+ /* Handle character unit type (4-byte vs 1-byte) */
+ if (unlikely (is_char4_unit (dtp)))
+ {
+ gfc_char4_t *out4 = (gfc_char4_t *) out;
+
+ /* Pad with spaces if width specified and we're short */
+ int pad_len = actual_width - output_len;
+ if (pad_len > 0)
+ memset4 (out4, ' ', pad_len);
+
+ /* Copy out the wide character string. */
+ out4 += (actual_width - output_len);
+ memcpy4 (out4, output, output_len);
+ }
+ else
+ {
+ /* Pad with spaces if width specified and we're short */
+ if (w > 0 && output_len < (size_t)actual_width)
+ memset (out, ' ', actual_width - output_len);
+ out += (actual_width - output_len);
+
+ /* Copy output string */
+ copy_len = (output_len < (size_t)actual_width)
+ ? output_len : (size_t)actual_width;
+ memcpy (out, output, copy_len);
+ }
+}
/* Set an fnode to default format. */
@@ -1938,7 +2129,7 @@ write_real (st_parameter_dt *dtp, const char *source, int kind)
buffer = select_buffer (dtp, &f, precision, buf_stack, &buf_size, kind);
get_float_string (dtp, &f, source , kind, 1, buffer,
- precision, buf_size, result, &flt_str_len);
+ precision, buf_size, result, &flt_str_len);
write_float_string (dtp, result, flt_str_len);
dtp->u.p.scale_factor = orig_scale;
@@ -2046,9 +2237,9 @@ write_complex (st_parameter_dt *dtp, const char *source, int kind, size_t size)
buffer = select_buffer (dtp, &f, precision, buf_stack, &buf_size, kind);
get_float_string (dtp, &f, source , kind, 0, buffer,
- precision, buf_size, result1, &flt_str_len1);
+ precision, buf_size, result1, &flt_str_len1);
get_float_string (dtp, &f, source + size / 2 , kind, 0, buffer,
- precision, buf_size, result2, &flt_str_len2);
+ precision, buf_size, result2, &flt_str_len2);
if (!dtp->u.p.namelist_mode)
{
lblanks = width - flt_str_len1 - flt_str_len2 - 3;
@@ -2344,10 +2535,10 @@ nml_write_obj (st_parameter_dt *dtp, namelist_info *obj, index_type offset,
len = strlen (base->var_name);
base_name_len = strlen (base_name);
for (dim_i = 0; dim_i < base_name_len; dim_i++)
- {
+ {
cup = safe_toupper (base_name[dim_i]);
write_character (dtp, &cup, 1, 1, NODELIM);
- }
+ }
}
clen = strlen (obj->var_name);
for (dim_i = len; dim_i < clen; dim_i++)
@@ -2440,28 +2631,28 @@ nml_write_obj (st_parameter_dt *dtp, namelist_info *obj, index_type offset,
case BT_INTEGER:
write_integer (dtp, p, len);
- break;
+ break;
case BT_LOGICAL:
write_logical (dtp, p, len);
- break;
+ break;
case BT_CHARACTER:
if (dtp->u.p.current_unit->flags.encoding == ENCODING_UTF8)
write_character (dtp, p, 4, obj->string_length, DELIM);
else
write_character (dtp, p, 1, obj->string_length, DELIM);
- break;
+ break;
case BT_REAL:
write_real (dtp, p, len);
- break;
+ break;
case BT_COMPLEX:
dtp->u.p.no_leading_blank = 0;
num++;
- write_complex (dtp, p, len, obj_size);
- break;
+ write_complex (dtp, p, len, obj_size);
+ break;
case BT_DERIVED:
case BT_CLASS:
@@ -2603,9 +2794,9 @@ nml_write_obj (st_parameter_dt *dtp, namelist_info *obj, index_type offset,
free (ext_name);
goto obj_loop;
- default:
+ default:
internal_error (&dtp->common, "Bad type for namelist write");
- }
+ }
/* Reset the leading blank suppression, write a comma (or semi-colon)
and, if 5 values have been output, write a newline and advance
@@ -2670,7 +2861,7 @@ namelist_write (st_parameter_dt *dtp)
switch (dtp->u.p.current_unit->delim_status)
{
case DELIM_APOSTROPHE:
- dtp->u.p.nml_delim = '\'';
+ dtp->u.p.nml_delim = '\'';
break;
case DELIM_QUOTE:
case DELIM_UNSPECIFIED:
diff --git a/libgfortran/io/write_float.def b/libgfortran/io/write_float.def
index 8732df49b28..608106af7c7 100644
--- a/libgfortran/io/write_float.def
+++ b/libgfortran/io/write_float.def
@@ -26,6 +26,295 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#include "config.h"
+/* Math function dispatch macros for kind=16. The type and its math functions
+ vary by platform: _Float128 (IEC 60559), __float128 (libquadmath, including
+ POWER_IEEE128), or 128-bit long double. */
+#ifdef HAVE_GFC_REAL_16
+# if defined(GFC_REAL_16_IS_FLOAT128)
+# if defined(GFC_REAL_16_USE_IEC_60559)
+# define GFC_REAL_16_FREXP(x, e) frexpf128 (x, e)
+# define GFC_REAL_16_FABS(x) fabsf128 (x)
+# define GFC_REAL_16_SCALBN(x, n) scalbnf128 (x, n)
+# else /* libquadmath __float128, including POWER_IEEE128 */
+# define GFC_REAL_16_FREXP(x, e) frexpq (x, e)
+# define GFC_REAL_16_FABS(x) fabsq (x)
+# define GFC_REAL_16_SCALBN(x, n) scalbnq (x, n)
+# endif
+# else /* 128-bit long double */
+# define GFC_REAL_16_FREXP(x, e) frexpl (x, e)
+# define GFC_REAL_16_FABS(x) fabsl (x)
+# define GFC_REAL_16_SCALBN(x, n) scalbnl (x, n)
+# endif
+#endif /* HAVE_GFC_REAL_16 */
+
+/* Helper function for EX format specifier.
+
+ Returns 0 on success, -1 on error. Fills 'buffer' with the hexadecimal
+ floating-point representation of the input value derived from the
+ IEEE-754 bit representations. Non-IEEE-754 representations are not
+ supported. Sets '*res_len' to the length of the string,
+ excluding NUL terminator. The buffer must be at least 64 bytes to
+ contain the resulting string for all kinds. */
+static int
+get_float_hex_string (const void *source, int kind, char *buffer,
+ int *res_len)
+{
+ int result = -1;
+ bool is_negative;
+ *res_len = 0;
+
+ switch (kind)
+ {
+ case 4:
+ {
+ GFC_REAL_4 val;
+ GFC_REAL_4 mant;
+ int expon;
+ int int_part;
+ unsigned int frac_part;
+
+ val = *(const GFC_REAL_4 *) source;
+ is_negative = signbit (val);
+ if (val == 0.0f)
+ {
+ if (is_negative)
+ result = snprintf (buffer, 9, "-0X0.P0");
+ else
+ result = snprintf (buffer, 8, "0X0.P0");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isinf (val))
+ {
+ if (is_negative)
+ result = snprintf (buffer, 5, "-Inf");
+ else
+ result = snprintf (buffer, 4, "Inf");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isnan (val))
+ {
+ result = snprintf (buffer, 4, "NaN");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ mant = frexpf (val, &expon);
+ /* Scale mantissa so the leading hex digit is in the range [8, 15]. */
+ if (mant != 0.0f)
+ {
+ mant = fabsf (mant);
+ mant = scalbnf (mant, 4);
+ expon -= 4;
+ if (mant < 8.f)
+ {
+ mant = scalbnf (mant, 1);
+ expon -= 1;
+ }
+ }
+ int_part = (int) mant;
+ /* 24 is the nearest integer divisible by 4 that is >= 23 (mantissa bits
+ for kind=4). (24-4)/4 = 5 hex digits for the fractional part. */
+ frac_part = (unsigned int) scalbnf (mant - (GFC_REAL_4) int_part, 24 - 4);
+ if (is_negative)
+ result = snprintf (buffer, 16, "-0X%X.%5.5XP%+d", int_part, frac_part, expon);
+ else
+ result = snprintf (buffer, 16, "0X%X.%5.5XP%+d", int_part, frac_part, expon);
+ }
+ break;
+ case 8:
+ {
+ double val;
+ double mant;
+ int expon;
+ int int_part;
+ unsigned long frac_part;
+
+ val = *(const GFC_REAL_8 *) source;
+ is_negative = signbit (val);
+ if (val == 0.0)
+ {
+ if (is_negative)
+ result = snprintf (buffer, 9, "-0X0.P0");
+ else
+ result = snprintf (buffer, 8, "0X0.P0");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isinf (val))
+ {
+ if (is_negative)
+ result = snprintf (buffer, 5, "-Inf");
+ else
+ result = snprintf (buffer, 4, "Inf");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isnan (val))
+ {
+ result = snprintf (buffer, 4, "NaN");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ mant = frexp (val, &expon);
+ /* Scale mantissa so the leading hex digit is in the range [8, 15]. */
+ if (mant != 0.0)
+ {
+ mant = fabs (mant);
+ mant = scalbn (mant, 4);
+ expon -= 4;
+ if (mant < 8.)
+ {
+ mant = scalbn (mant, 1);
+ expon -= 1;
+ }
+ }
+ int_part = (int) mant;
+ /* 56 is the nearest integer divisible by 4 that is >= 53 (mantissa bits
+ for kind=8). (56-4)/4 = 13 hex digits for the fractional part. */
+ frac_part = (unsigned long) scalbn (mant - (double) int_part, 56 - 4);
+ if (is_negative)
+ result = snprintf (buffer, 25, "-0X%X.%13.13lXP%+d", int_part, frac_part, expon);
+ else
+ result = snprintf (buffer, 25, "0X%X.%13.13lXP%+d", int_part, frac_part, expon);
+ }
+ break;
+#ifdef HAVE_GFC_REAL_10
+ case 10:
+ {
+ GFC_REAL_10 val;
+ GFC_REAL_10 mant;
+ int expon;
+ int int_part;
+ unsigned long long frac_part;
+
+ val = *(const GFC_REAL_10 *) source;
+ is_negative = signbit (val);
+ if (val == 0.0L)
+ {
+ if (is_negative)
+ result = snprintf (buffer, 9, "-0X0.P0");
+ else
+ result = snprintf (buffer, 8, "0X0.P0");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isinf (val))
+ {
+ if (is_negative)
+ result = snprintf (buffer, 5, "-Inf");
+ else
+ result = snprintf (buffer, 4, "Inf");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isnan (val))
+ {
+ result = snprintf (buffer, 4, "NaN");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ mant = frexpl (val, &expon);
+ /* Scale mantissa so the leading hex digit is in the range [8, 15]. */
+ if (mant != 0.0L)
+ {
+ mant = fabsl (mant);
+ mant = scalbnl (mant, 4);
+ expon -= 4;
+ if (mant < 8.L)
+ {
+ mant = scalbnl (mant, 1);
+ expon -= 1;
+ }
+ }
+ int_part = (int) mant;
+ /* 64 is the nearest integer divisible by 4 that is >= 64 (mantissa bits
+ for kind=10). (64-4)/4 = 15 hex digits for the fractional part. */
+ frac_part = (unsigned long long) scalbnl (mant - (GFC_REAL_10) int_part, 64 - 4);
+ if (is_negative)
+ result = snprintf (buffer, 28, "-0X%X.%15.15llXP%+d", int_part, frac_part, expon);
+ else
+ result = snprintf (buffer, 28, "0X%X.%15.15llXP%+d", int_part, frac_part, expon);
+ }
+ break;
+#endif
+#ifdef HAVE_GFC_REAL_16
+ case 16:
+ {
+ GFC_REAL_16 val;
+ GFC_REAL_16 mant;
+ int expon;
+ int int_part;
+ unsigned long long frac_hi, frac_lo;
+ GFC_REAL_16 frac_val, frac_lo_val;
+
+ val = *(const GFC_REAL_16 *) source;
+ is_negative = signbit (val);
+ if (val == (GFC_REAL_16) 0.0)
+ {
+ if (is_negative)
+ result = snprintf (buffer, 9, "-0X0.P0");
+ else
+ result = snprintf (buffer, 8, "0X0.P0");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isinf (val))
+ {
+ if (is_negative)
+ result = snprintf (buffer, 5, "-Inf");
+ else
+ result = snprintf (buffer, 4, "Inf");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ if (isnan (val))
+ {
+ result = snprintf (buffer, 4, "NaN");
+ *res_len = result;
+ return result < 0 ? -1 : 0;
+ }
+ mant = GFC_REAL_16_FREXP (val, &expon);
+ /* Scale mantissa so the leading hex digit is in the range [8, 15]. */
+ if (mant != (GFC_REAL_16) 0.0)
+ {
+ mant = GFC_REAL_16_FABS (mant);
+ mant = GFC_REAL_16_SCALBN (mant, 4);
+ expon -= 4;
+ if (mant < (GFC_REAL_16) 8.)
+ {
+ mant = GFC_REAL_16_SCALBN (mant, 1);
+ expon -= 1;
+ }
+ }
+ int_part = (int) mant;
+ /* 116 is the nearest integer divisible by 4 that is >= 113 (mantissa
+ bits for kind=16). (116-4)/4 = 28 hex digits for the fractional
+ part, split into two 56-bit halves (14 hex digits each) to fit in
+ unsigned long long. */
+ frac_val = mant - (GFC_REAL_16) int_part;
+ frac_hi = (unsigned long long) GFC_REAL_16_SCALBN (frac_val, 56);
+ frac_lo_val = frac_val - GFC_REAL_16_SCALBN ((GFC_REAL_16) frac_hi, -56);
+ frac_lo = (unsigned long long) GFC_REAL_16_SCALBN (frac_lo_val, 112);
+ if (is_negative)
+ result = snprintf (buffer, 42, "-0X%X.%14.14llX%14.14llXP%+d",
+ int_part, frac_hi, frac_lo, expon);
+ else
+ result = snprintf (buffer, 42, "0X%X.%14.14llX%14.14llXP%+d",
+ int_part, frac_hi, frac_lo, expon);
+ }
+ break;
+#endif /* HAVE_GFC_REAL_16 */
+ default:
+ return -1;
+ }
+ if (result < 0)
+ return -1;
+
+ *res_len = result;
+ return 0;
+}
+
typedef enum
{ S_NONE, S_MINUS, S_PLUS }
sign_t;
@@ -186,7 +475,7 @@ build_float_string (st_parameter_dt *dtp, const fnode *f, char *buffer,
case FMT_F:
nbefore = ndigits - precision;
if ((w > 0) && (nbefore > (int) size))
- {
+ {
*len = w;
star_fill (result, w);
result[w] = '\0';
@@ -310,7 +599,7 @@ build_float_string (st_parameter_dt *dtp, const fnode *f, char *buffer,
/* The exponent must be a multiple of three, with 1-3 digits before
the decimal point. */
if (!zero_flag)
- e--;
+ e--;
if (e >= 0)
nbefore = e % 3;
else
@@ -328,7 +617,7 @@ build_float_string (st_parameter_dt *dtp, const fnode *f, char *buffer,
case FMT_ES:
if (!zero_flag)
- e--;
+ e--;
nbefore = 1;
nzero = 0;
nafter = d;
@@ -444,9 +733,9 @@ build_float_string (st_parameter_dt *dtp, const fnode *f, char *buffer,
if (i < 0)
{
/* The carry overflowed. Fortunately we have some spare
- space at the start of the buffer. We may discard some
- digits, but this is ok because we already know they are
- zero. */
+ space at the start of the buffer. We may discard some
+ digits, but this is ok because we already know they are
+ zero. */
digits--;
digits[0] = '1';
if (ft == FMT_F)
@@ -562,7 +851,7 @@ build_float_string (st_parameter_dt *dtp, const fnode *f, char *buffer,
/* The output is zero, so set the sign according to the sign bit unless
-fno-sign-zero was specified. */
if (compile_options.sign_zero == 1)
- sign = calculate_sign (dtp, sign_bit);
+ sign = calculate_sign (dtp, sign_bit);
else
sign = calculate_sign (dtp, 0);
}
@@ -987,9 +1276,9 @@ quadmath_snprintf (buffer, size, "%+-#.*Qf", (prec), (val))
10.0**e even when the final result will not be rounded to 10.0**e.
For these values the exponent returned by atoi has to be decremented
by one. The values y in the ranges
- (1000.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*(n+1))
- (100.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*n+2)
- (10.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*n+1)
+ (1000.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*(n+1))
+ (100.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*n+2)
+ (10.0-0.5*10.0**(-d))*10.0**(3*n) <= y < 10.0*(3*n+1)
are correctly rounded respectively to 1.0...0*10.0*(3*(n+1)),
100.0...0*10.0*(3*n), and 10.0...0*10.0*(3*n), where 0...0
represents d zeroes, by the lines 279 to 297. */
@@ -1118,7 +1407,7 @@ determine_en_precision (st_parameter_dt *dtp, const fnode *f,
d = precision;\
}\
/* The switch between FMT_E and FMT_F is based on the absolute value. \
- Set r=0 for rounding toward zero and r = 1 otherwise. \
+ Set r=0 for rounding toward zero and r = 1 otherwise. \
If (exp_d - m) == 1 there is no rounding needed. */\
switch (dtp->u.p.current_unit->round_status)\
{\
--
2.54.0