[flang] Update output format test to use GTest

Better document each test in output formatting tests. Use GTest primitives and infrastructure in same
spirit as [[ https://reviews.llvm.org/D97403 | D97403 ]]. [[ https://github.com/flang-compiler/f18/issues/995#issuecomment-790737912 | See legacy github issue linked here ]] for additional context. Reorganize long test cases to be more readable.

Reviewed By: awarzynski, klausler

Differential Revision: https://reviews.llvm.org/D98303

GitOrigin-RevId: e8515ca8478f96f7d2eddadc4d310ac29bb04abe
diff --git a/unittests/Runtime/CMakeLists.txt b/unittests/Runtime/CMakeLists.txt
index 616c86f..cc7ac72 100644
--- a/unittests/Runtime/CMakeLists.txt
+++ b/unittests/Runtime/CMakeLists.txt
@@ -23,11 +23,6 @@
   FortranRuntime
 )
 
-add_flang_nongtest_unittest(hello
-  RuntimeTesting
-  FortranRuntime
-)
-
 # This test is not run by default as it requires input.
 add_executable(external-hello-world
   external-hello.cpp
diff --git a/unittests/Runtime/hello.cpp b/unittests/Runtime/hello.cpp
deleted file mode 100644
index d17c98e..0000000
--- a/unittests/Runtime/hello.cpp
+++ /dev/null
@@ -1,526 +0,0 @@
-// Basic sanity tests of I/O API; exhaustive testing will be done in Fortran
-
-#include "testing.h"
-#include "../../runtime/descriptor.h"
-#include "../../runtime/io-api.h"
-#include <cstring>
-
-using namespace Fortran::runtime;
-using namespace Fortran::runtime::io;
-
-static bool test(const char *format, const char *expect, std::string &&got) {
-  std::string want{expect};
-  want.resize(got.length(), ' ');
-  if (got != want) {
-    Fail() << '\'' << format << "' failed;\n     got '" << got
-           << "',\nexpected '" << want << "'\n";
-    return false;
-  }
-  return true;
-}
-
-static void hello() {
-  char buffer[32];
-  const char *format{"(6HHELLO,,A6,2X,I3,1X,'0x',Z8,1X,L1)"};
-  auto cookie{IONAME(BeginInternalFormattedOutput)(
-      buffer, sizeof buffer, format, std::strlen(format))};
-  IONAME(OutputAscii)(cookie, "WORLD", 5);
-  IONAME(OutputInteger64)(cookie, 678);
-  IONAME(OutputInteger64)(cookie, 0xfeedface);
-  IONAME(OutputLogical)(cookie, true);
-  if (auto status{IONAME(EndIoStatement)(cookie)}) {
-    Fail() << "hello: '" << format << "' failed, status "
-           << static_cast<int>(status) << '\n';
-  } else {
-    test(format, "HELLO, WORLD  678 0xFEEDFACE T",
-        std::string{buffer, sizeof buffer});
-  }
-}
-
-static void multiline() {
-  char buffer[5][32];
-  StaticDescriptor<1> staticDescriptor[2];
-  Descriptor &whole{staticDescriptor[0].descriptor()};
-  SubscriptValue extent[]{5};
-  whole.Establish(TypeCode{CFI_type_char}, sizeof buffer[0], &buffer, 1, extent,
-      CFI_attribute_pointer);
-  whole.Dump();
-  whole.Check();
-  Descriptor &section{staticDescriptor[1].descriptor()};
-  SubscriptValue lowers[]{0}, uppers[]{4}, strides[]{1};
-  section.Establish(whole.type(), whole.ElementBytes(), nullptr, 1, extent,
-      CFI_attribute_pointer);
-  if (auto error{
-          CFI_section(&section.raw(), &whole.raw(), lowers, uppers, strides)}) {
-    Fail() << "multiline: CFI_section failed: " << error << '\n';
-    return;
-  }
-  section.Dump();
-  section.Check();
-  const char *format{
-      "('?abcde,',T1,'>',T9,A,TL12,A,TR25,'<'//G0,17X,'abcd',1(2I4))"};
-  auto cookie{IONAME(BeginInternalArrayFormattedOutput)(
-      section, format, std::strlen(format))};
-  IONAME(OutputAscii)(cookie, "WORLD", 5);
-  IONAME(OutputAscii)(cookie, "HELLO", 5);
-  IONAME(OutputInteger64)(cookie, 789);
-  for (int j{666}; j <= 999; j += 111) {
-    IONAME(OutputInteger64)(cookie, j);
-  }
-  if (auto status{IONAME(EndIoStatement)(cookie)}) {
-    Fail() << "multiline: '" << format << "' failed, status "
-           << static_cast<int>(status) << '\n';
-  } else {
-    test(format,
-        ">HELLO, WORLD                  <"
-        "                                "
-        "789                 abcd 666 777"
-        " 888 999                        "
-        "                                ",
-        std::string{buffer[0], sizeof buffer});
-  }
-}
-
-static void listInputTest() {
-  static const char input[]{",1*,(5.,6..)"};
-  auto cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)};
-  float z[6];
-  for (int j{0}; j < 6; ++j) {
-    z[j] = -(j + 1);
-  }
-  for (int j{0}; j < 6; j += 2) {
-    if (!IONAME(InputComplex32)(cookie, &z[j])) {
-      Fail() << "InputComplex32 failed\n";
-    }
-  }
-  auto status{IONAME(EndIoStatement)(cookie)};
-  if (status) {
-    Fail() << "Failed complex list-directed input, status "
-           << static_cast<int>(status) << '\n';
-  } else {
-    char output[33];
-    output[32] = '\0';
-    cookie = IONAME(BeginInternalListOutput)(output, 32);
-    for (int j{0}; j < 6; j += 2) {
-      if (!IONAME(OutputComplex32)(cookie, z[j], z[j + 1])) {
-        Fail() << "OutputComplex32 failed\n";
-      }
-    }
-    status = IONAME(EndIoStatement)(cookie);
-    static const char expect[33]{" (-1.,-2.) (-3.,-4.) (5.,6.)    "};
-    if (status) {
-      Fail() << "Failed complex list-directed output, status "
-             << static_cast<int>(status) << '\n';
-    } else if (std::strncmp(output, expect, 33) != 0) {
-      Fail() << "Failed complex list-directed output, expected '" << expect
-             << "', but got '" << output << "'\n";
-    }
-  }
-}
-
-static void descrOutputTest() {
-  char buffer[9];
-  // Formatted
-  const char *format{"(2A4)"};
-  auto cookie{IONAME(BeginInternalFormattedOutput)(
-      buffer, sizeof buffer, format, std::strlen(format))};
-  StaticDescriptor<1> staticDescriptor;
-  Descriptor &desc{staticDescriptor.descriptor()};
-  SubscriptValue extent[]{2};
-  char data[2][4];
-  std::memcpy(data[0], "ABCD", 4);
-  std::memcpy(data[1], "EFGH", 4);
-  desc.Establish(TypeCode{CFI_type_char}, sizeof data[0], &data, 1, extent);
-  desc.Dump();
-  desc.Check();
-  IONAME(OutputDescriptor)(cookie, desc);
-  if (auto status{IONAME(EndIoStatement)(cookie)}) {
-    Fail() << "descrOutputTest: '" << format << "' failed, status "
-           << static_cast<int>(status) << '\n';
-  } else {
-    test("descrOutputTest(formatted)", "ABCDEFGH ",
-        std::string{buffer, sizeof buffer});
-  }
-  // List-directed
-  cookie = IONAME(BeginInternalListOutput)(buffer, sizeof buffer);
-  IONAME(OutputDescriptor)(cookie, desc);
-  if (auto status{IONAME(EndIoStatement)(cookie)}) {
-    Fail() << "descrOutputTest: list-directed failed, status "
-           << static_cast<int>(status) << '\n';
-  } else {
-    test("descrOutputTest(list)", " ABCDEFGH",
-        std::string{buffer, sizeof buffer});
-  }
-}
-
-static void realTest(const char *format, double x, const char *expect) {
-  char buffer[800];
-  auto cookie{IONAME(BeginInternalFormattedOutput)(
-      buffer, sizeof buffer, format, std::strlen(format))};
-  IONAME(OutputReal64)(cookie, x);
-  if (auto status{IONAME(EndIoStatement)(cookie)}) {
-    Fail() << '\'' << format << "' failed, status " << static_cast<int>(status)
-           << '\n';
-  } else {
-    test(format, expect, std::string{buffer, sizeof buffer});
-  }
-}
-
-static void realInTest(
-    const char *format, const char *data, std::uint64_t want) {
-  auto cookie{IONAME(BeginInternalFormattedInput)(
-      data, std::strlen(data), format, std::strlen(format))};
-  union {
-    double x;
-    std::uint64_t raw;
-  } u;
-  u.raw = 0;
-  IONAME(EnableHandlers)(cookie, true, true, true, true, true);
-  IONAME(InputReal64)(cookie, u.x);
-  char iomsg[65];
-  iomsg[0] = '\0';
-  iomsg[sizeof iomsg - 1] = '\0';
-  IONAME(GetIoMsg)(cookie, iomsg, sizeof iomsg - 1);
-  auto status{IONAME(EndIoStatement)(cookie)};
-  if (status) {
-    Fail() << '\'' << format << "' failed reading '" << data << "', status "
-           << static_cast<int>(status) << " iomsg '" << iomsg << "'\n";
-  } else if (u.raw != want) {
-    Fail() << '\'' << format << "' failed reading '" << data << "', want 0x";
-    Fail().write_hex(want) << ", got 0x" << u.raw << '\n';
-  }
-}
-
-int main() {
-  StartTests();
-
-  hello();
-  multiline();
-
-  static const char *zeroes[][2]{
-      {"(E32.17,';')", "         0.00000000000000000E+00;"},
-      {"(F32.17,';')", "             0.00000000000000000;"},
-      {"(G32.17,';')", "          0.0000000000000000    ;"},
-      {"(DC,E32.17,';')", "         0,00000000000000000E+00;"},
-      {"(DC,F32.17,';')", "             0,00000000000000000;"},
-      {"(DC,G32.17,';')", "          0,0000000000000000    ;"},
-      {"(D32.17,';')", "         0.00000000000000000D+00;"},
-      {"(E32.17E1,';')", "          0.00000000000000000E+0;"},
-      {"(G32.17E1,';')", "           0.0000000000000000   ;"},
-      {"(E32.17E0,';')", "          0.00000000000000000E+0;"},
-      {"(G32.17E0,';')", "          0.0000000000000000    ;"},
-      {"(1P,E32.17,';')", "         0.00000000000000000E+00;"},
-      {"(1PE32.17,';')", "         0.00000000000000000E+00;"}, // no comma
-      {"(1P,F32.17,';')", "             0.00000000000000000;"},
-      {"(1P,G32.17,';')", "          0.0000000000000000    ;"},
-      {"(2P,E32.17,';')", "         00.0000000000000000E+00;"},
-      {"(-1P,E32.17,';')", "         0.00000000000000000E+00;"},
-      {"(G0,';')", "0.;"}, {}};
-  for (int j{0}; zeroes[j][0]; ++j) {
-    realTest(zeroes[j][0], 0.0, zeroes[j][1]);
-  }
-
-  static const char *ones[][2]{
-      {"(E32.17,';')", "         0.10000000000000000E+01;"},
-      {"(F32.17,';')", "             1.00000000000000000;"},
-      {"(G32.17,';')", "          1.0000000000000000    ;"},
-      {"(E32.17E1,';')", "          0.10000000000000000E+1;"},
-      {"(G32.17E1,';')", "           1.0000000000000000   ;"},
-      {"(E32.17E0,';')", "          0.10000000000000000E+1;"},
-      {"(G32.17E0,';')", "          1.0000000000000000    ;"},
-      {"(E32.17E4,';')", "       0.10000000000000000E+0001;"},
-      {"(G32.17E4,';')", "        1.0000000000000000      ;"},
-      {"(1P,E32.17,';')", "         1.00000000000000000E+00;"},
-      {"(1PE32.17,';')", "         1.00000000000000000E+00;"}, // no comma
-      {"(1P,F32.17,';')", "            10.00000000000000000;"},
-      {"(1P,G32.17,';')", "          1.0000000000000000    ;"},
-      {"(ES32.17,';')", "         1.00000000000000000E+00;"},
-      {"(2P,E32.17,';')", "         10.0000000000000000E-01;"},
-      {"(2P,G32.17,';')", "          1.0000000000000000    ;"},
-      {"(-1P,E32.17,';')", "         0.01000000000000000E+02;"},
-      {"(-1P,G32.17,';')", "          1.0000000000000000    ;"},
-      {"(G0,';')", "1.;"}, {}};
-  for (int j{0}; ones[j][0]; ++j) {
-    realTest(ones[j][0], 1.0, ones[j][1]);
-  }
-
-  realTest("(E32.17,';')", -1.0, "        -0.10000000000000000E+01;");
-  realTest("(F32.17,';')", -1.0, "            -1.00000000000000000;");
-  realTest("(G32.17,';')", -1.0, "         -1.0000000000000000    ;");
-  realTest("(G0,';')", -1.0, "-1.;");
-
-  volatile union {
-    double d;
-    std::uint64_t n;
-  } u;
-  u.n = 0x8000000000000000; // -0
-  realTest("(E9.1,';')", u.d, " -0.0E+00;");
-  realTest("(F4.0,';')", u.d, " -0.;");
-  realTest("(G8.0,';')", u.d, "-0.0E+00;");
-  realTest("(G8.1,';')", u.d, " -0.    ;");
-  realTest("(G0,';')", u.d, "-0.;");
-  u.n = 0x7ff0000000000000; // +Inf
-  realTest("(E9.1,';')", u.d, "      Inf;");
-  realTest("(F9.1,';')", u.d, "      Inf;");
-  realTest("(G9.1,';')", u.d, "      Inf;");
-  realTest("(SP,E9.1,';')", u.d, "     +Inf;");
-  realTest("(SP,F9.1,';')", u.d, "     +Inf;");
-  realTest("(SP,G9.1,';')", u.d, "     +Inf;");
-  realTest("(G0,';')", u.d, "Inf;");
-  u.n = 0xfff0000000000000; // -Inf
-  realTest("(E9.1,';')", u.d, "     -Inf;");
-  realTest("(F9.1,';')", u.d, "     -Inf;");
-  realTest("(G9.1,';')", u.d, "     -Inf;");
-  realTest("(G0,';')", u.d, "-Inf;");
-  u.n = 0x7ff0000000000001; // NaN
-  realTest("(E9.1,';')", u.d, "      NaN;");
-  realTest("(F9.1,';')", u.d, "      NaN;");
-  realTest("(G9.1,';')", u.d, "      NaN;");
-  realTest("(G0,';')", u.d, "NaN;");
-  u.n = 0xfff0000000000001; // NaN (sign irrelevant)
-  realTest("(E9.1,';')", u.d, "      NaN;");
-  realTest("(F9.1,';')", u.d, "      NaN;");
-  realTest("(G9.1,';')", u.d, "      NaN;");
-  realTest("(SP,E9.1,';')", u.d, "      NaN;");
-  realTest("(SP,F9.1,';')", u.d, "      NaN;");
-  realTest("(SP,G9.1,';')", u.d, "      NaN;");
-  realTest("(G0,';')", u.d, "NaN;");
-
-  u.n = 0x3fb999999999999a; // 0.1 rounded
-  realTest("(E62.55,';')", u.d,
-      " 0.1000000000000000055511151231257827021181583404541015625E+00;");
-  realTest("(E0.0,';')", u.d, "0.E+00;");
-  realTest("(E0.55,';')", u.d,
-      "0.1000000000000000055511151231257827021181583404541015625E+00;");
-  realTest("(E0,';')", u.d, ".1E+00;");
-  realTest("(F58.55,';')", u.d,
-      " 0.1000000000000000055511151231257827021181583404541015625;");
-  realTest("(F0.0,';')", u.d, "0.;");
-  realTest("(F0.55,';')", u.d,
-      ".1000000000000000055511151231257827021181583404541015625;");
-  realTest("(F0,';')", u.d, ".1;");
-  realTest("(G62.55,';')", u.d,
-      " 0.1000000000000000055511151231257827021181583404541015625    ;");
-  realTest("(G0.0,';')", u.d, "0.;");
-  realTest("(G0.55,';')", u.d,
-      ".1000000000000000055511151231257827021181583404541015625;");
-  realTest("(G0,';')", u.d, ".1;");
-
-  u.n = 0x3ff8000000000000; // 1.5
-  realTest("(E9.2,';')", u.d, " 0.15E+01;");
-  realTest("(F4.1,';')", u.d, " 1.5;");
-  realTest("(G7.1,';')", u.d, " 2.    ;");
-  realTest("(RN,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RN,F3.0,';')", u.d, " 2.;");
-  realTest("(RN,G7.0,';')", u.d, " 0.E+01;");
-  realTest("(RN,G7.1,';')", u.d, " 2.    ;");
-  realTest("(RD,E8.1,';')", u.d, " 0.1E+01;");
-  realTest("(RD,F3.0,';')", u.d, " 1.;");
-  realTest("(RD,G7.0,';')", u.d, " 0.E+01;");
-  realTest("(RD,G7.1,';')", u.d, " 1.    ;");
-  realTest("(RU,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RU,G7.0,';')", u.d, " 0.E+01;");
-  realTest("(RU,G7.1,';')", u.d, " 2.    ;");
-  realTest("(RZ,E8.1,';')", u.d, " 0.1E+01;");
-  realTest("(RZ,F3.0,';')", u.d, " 1.;");
-  realTest("(RZ,G7.0,';')", u.d, " 0.E+01;");
-  realTest("(RZ,G7.1,';')", u.d, " 1.    ;");
-  realTest("(RC,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RC,F3.0,';')", u.d, " 2.;");
-  realTest("(RC,G7.0,';')", u.d, " 0.E+01;");
-  realTest("(RC,G7.1,';')", u.d, " 2.    ;");
-
-  // TODO continue F and G editing tests on these data
-
-  u.n = 0xbff8000000000000; // -1.5
-  realTest("(E9.2,';')", u.d, "-0.15E+01;");
-  realTest("(RN,E8.1,';')", u.d, "-0.2E+01;");
-  realTest("(RD,E8.1,';')", u.d, "-0.2E+01;");
-  realTest("(RU,E8.1,';')", u.d, "-0.1E+01;");
-  realTest("(RZ,E8.1,';')", u.d, "-0.1E+01;");
-  realTest("(RC,E8.1,';')", u.d, "-0.2E+01;");
-
-  u.n = 0x4004000000000000; // 2.5
-  realTest("(E9.2,';')", u.d, " 0.25E+01;");
-  realTest("(RN,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RD,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RU,E8.1,';')", u.d, " 0.3E+01;");
-  realTest("(RZ,E8.1,';')", u.d, " 0.2E+01;");
-  realTest("(RC,E8.1,';')", u.d, " 0.3E+01;");
-
-  u.n = 0xc004000000000000; // -2.5
-  realTest("(E9.2,';')", u.d, "-0.25E+01;");
-  realTest("(RN,E8.1,';')", u.d, "-0.2E+01;");
-  realTest("(RD,E8.1,';')", u.d, "-0.3E+01;");
-  realTest("(RU,E8.1,';')", u.d, "-0.2E+01;");
-  realTest("(RZ,E8.1,';')", u.d, "-0.2E+01;");
-  realTest("(RC,E8.1,';')", u.d, "-0.3E+01;");
-
-  u.n = 1; // least positive nonzero subnormal
-  realTest("(E32.17,';')", u.d, "         0.49406564584124654-323;");
-  realTest("(ES32.17,';')", u.d, "         4.94065645841246544-324;");
-  realTest("(EN32.17,';')", u.d, "         4.94065645841246544-324;");
-  realTest("(E759.752,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "75386825064197182655334472656250-323;");
-  realTest("(G0,';')", u.d, ".5-323;");
-  realTest("(E757.750,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "753868250641971826553344726562-323;");
-  realTest("(RN,E757.750,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "753868250641971826553344726562-323;");
-  realTest("(RD,E757.750,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "753868250641971826553344726562-323;");
-  realTest("(RU,E757.750,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "753868250641971826553344726563-323;");
-  realTest("(RC,E757.750,';')", u.d,
-      " 0."
-      "494065645841246544176568792868221372365059802614324764425585682500675507"
-      "270208751865299836361635992379796564695445717730926656710355939796398774"
-      "796010781878126300713190311404527845817167848982103688718636056998730723"
-      "050006387409153564984387312473397273169615140031715385398074126238565591"
-      "171026658556686768187039560310624931945271591492455329305456544401127480"
-      "129709999541931989409080416563324524757147869014726780159355238611550134"
-      "803526493472019379026810710749170333222684475333572083243193609238289345"
-      "836806010601150616980975307834227731832924790498252473077637592724787465"
-      "608477820373446969953364701797267771758512566055119913150489110145103786"
-      "273816725095583738973359899366480994116420570263709027924276754456522908"
-      "753868250641971826553344726563-323;");
-
-  u.n = 0x10000000000000; // least positive nonzero normal
-  realTest("(E723.716,';')", u.d,
-      " 0."
-      "222507385850720138309023271733240406421921598046233183055332741688720443"
-      "481391819585428315901251102056406733973103581100515243416155346010885601"
-      "238537771882113077799353200233047961014744258363607192156504694250373420"
-      "837525080665061665815894872049117996859163964850063590877011830487479978"
-      "088775374994945158045160505091539985658247081864511353793580499211598108"
-      "576605199243335211435239014879569960959128889160299264151106346631339366"
-      "347758651302937176204732563178148566435087212282863764204484681140761391"
-      "147706280168985324411002416144742161856716615054015428508471675290190316"
-      "132277889672970737312333408698898317506783884692609277397797285865965494"
-      "10913690954061364675687023986783152906809846172109246253967285156250-"
-      "307;");
-  realTest("(G0,';')", u.d, ".22250738585072014-307;");
-
-  u.n = 0x7fefffffffffffffuLL; // greatest finite
-  realTest("(E32.17,';')", u.d, "         0.17976931348623157+309;");
-  realTest("(E317.310,';')", u.d,
-      " 0."
-      "179769313486231570814527423731704356798070567525844996598917476803157260"
-      "780028538760589558632766878171540458953514382464234321326889464182768467"
-      "546703537516986049910576551282076245490090389328944075868508455133942304"
-      "583236903222948165808559332123348274797826204144723168738177180919299881"
-      "2504040261841248583680+309;");
-  realTest("(ES317.310,';')", u.d,
-      " 1."
-      "797693134862315708145274237317043567980705675258449965989174768031572607"
-      "800285387605895586327668781715404589535143824642343213268894641827684675"
-      "467035375169860499105765512820762454900903893289440758685084551339423045"
-      "832369032229481658085593321233482747978262041447231687381771809192998812"
-      "5040402618412485836800+308;");
-  realTest("(EN319.310,';')", u.d,
-      " 179."
-      "769313486231570814527423731704356798070567525844996598917476803157260780"
-      "028538760589558632766878171540458953514382464234321326889464182768467546"
-      "703537516986049910576551282076245490090389328944075868508455133942304583"
-      "236903222948165808559332123348274797826204144723168738177180919299881250"
-      "4040261841248583680000+306;");
-  realTest("(G0,';')", u.d, ".17976931348623157+309;");
-
-  realTest("(F5.3,';')", 25., "*****;");
-  realTest("(F5.3,';')", 2.5, "2.500;");
-  realTest("(F5.3,';')", 0.25, "0.250;");
-  realTest("(F5.3,';')", 0.025, "0.025;");
-  realTest("(F5.3,';')", 0.0025, "0.003;");
-  realTest("(F5.3,';')", 0.00025, "0.000;");
-  realTest("(F5.3,';')", 0.000025, "0.000;");
-  realTest("(F5.3,';')", -25., "*****;");
-  realTest("(F5.3,';')", -2.5, "*****;");
-  realTest("(F5.3,';')", -0.25, "-.250;");
-  realTest("(F5.3,';')", -0.025, "-.025;");
-  realTest("(F5.3,';')", -0.0025, "-.003;");
-  realTest("(F5.3,';')", -0.00025, "-.000;");
-  realTest("(F5.3,';')", -0.000025, "-.000;");
-
-  realInTest("(F18.0)", "                 0", 0x0);
-  realInTest("(F18.0)", "                  ", 0x0);
-  realInTest("(F18.0)", "                -0", 0x8000000000000000);
-  realInTest("(F18.0)", "                01", 0x3ff0000000000000);
-  realInTest("(F18.0)", "                 1", 0x3ff0000000000000);
-  realInTest("(F18.0)", "              125.", 0x405f400000000000);
-  realInTest("(F18.0)", "              12.5", 0x4029000000000000);
-  realInTest("(F18.0)", "              1.25", 0x3ff4000000000000);
-  realInTest("(F18.0)", "             01.25", 0x3ff4000000000000);
-  realInTest("(F18.0)", "              .125", 0x3fc0000000000000);
-  realInTest("(F18.0)", "             0.125", 0x3fc0000000000000);
-  realInTest("(F18.0)", "             .0625", 0x3fb0000000000000);
-  realInTest("(F18.0)", "            0.0625", 0x3fb0000000000000);
-  realInTest("(F18.0)", "               125", 0x405f400000000000);
-  realInTest("(F18.1)", "               125", 0x4029000000000000);
-  realInTest("(F18.2)", "               125", 0x3ff4000000000000);
-  realInTest("(F18.3)", "               125", 0x3fc0000000000000);
-  realInTest("(-1P,F18.0)", "               125", 0x4093880000000000); // 1250
-  realInTest("(1P,F18.0)", "               125", 0x4029000000000000); // 12.5
-  realInTest("(BZ,F18.0)", "              125 ", 0x4093880000000000); // 1250
-  realInTest("(BZ,F18.0)", "       125 . e +1 ", 0x42a6bcc41e900000); // 1.25e13
-  realInTest("(DC,F18.0)", "              12,5", 0x4029000000000000);
-
-  listInputTest();
-  descrOutputTest();
-
-  return EndTests();
-}
diff --git a/unittests/RuntimeGTest/CMakeLists.txt b/unittests/RuntimeGTest/CMakeLists.txt
index f26cb44..d4ad6b2 100644
--- a/unittests/RuntimeGTest/CMakeLists.txt
+++ b/unittests/RuntimeGTest/CMakeLists.txt
@@ -2,6 +2,9 @@
   CharacterTest.cpp
   RuntimeCrashTest.cpp
   CrashHandlerFixture.cpp
+  NumericalFormatTest.cpp
+  RuntimeCrashTest.cpp
+  CrashHandlerFixture.cpp
 )
 
 target_link_libraries(FlangRuntimeTests
diff --git a/unittests/RuntimeGTest/NumericalFormatTest.cpp b/unittests/RuntimeGTest/NumericalFormatTest.cpp
new file mode 100644
index 0000000..7788c43
--- /dev/null
+++ b/unittests/RuntimeGTest/NumericalFormatTest.cpp
@@ -0,0 +1,694 @@
+//===-- flang/unittests/RuntimeGTest/NumericalFormatTest.cpp ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CrashHandlerFixture.h"
+#include "../../runtime/descriptor.h"
+#include "../../runtime/io-api.h"
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <gtest/gtest.h>
+#include <tuple>
+
+using namespace Fortran::runtime;
+using namespace Fortran::runtime::io;
+
+static bool CompareFormattedStrings(
+    const std::string &expect, const std::string &&got) {
+  std::string want{expect};
+  want.resize(got.size(), ' ');
+  return want == got;
+}
+
+static bool CompareFormattedStrings(
+    const char *expect, const std::string &&got) {
+  return CompareFormattedStrings(std::string(expect), std::move(got));
+}
+
+// Perform format and compare the result with expected value
+static bool CompareFormatReal(
+    const char *format, double x, const char *expect) {
+  char buffer[800];
+  auto *cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, sizeof buffer, format, std::strlen(format))};
+  IONAME(OutputReal64)(cookie, x);
+  auto status{IONAME(EndIoStatement)(cookie)};
+
+  EXPECT_EQ(status, 0);
+  return CompareFormattedStrings(expect, std::string{buffer, sizeof buffer});
+}
+
+// Convert raw uint64 into double, perform format, and compare with expected
+static bool CompareFormatReal(
+    const char *format, std::uint64_t xInt, const char *expect) {
+  double x;
+  static_assert(sizeof(double) == sizeof(std::uint64_t),
+      "Size of double != size of uint64_t!");
+  std::memcpy(&x, &xInt, sizeof xInt);
+  return CompareFormatReal(format, x, expect);
+}
+
+struct IOApiTests : CrashHandlerFixture {};
+
+TEST(IOApiTests, HelloWorldOutputTest) {
+  static constexpr int bufferSize{32};
+  char buffer[bufferSize];
+
+  // Create format for all types and values to be written
+  const char *format{"(6HHELLO,,A6,2X,I3,1X,'0x',Z8,1X,L1)"};
+  auto *cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, bufferSize, format, std::strlen(format))};
+
+  // Write string, integer, and logical values to buffer
+  IONAME(OutputAscii)(cookie, "WORLD", 5);
+  IONAME(OutputInteger64)(cookie, 678);
+  IONAME(OutputInteger64)(cookie, 0xfeedface);
+  IONAME(OutputLogical)(cookie, true);
+
+  // Ensure IO succeeded
+  auto status{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(status, 0) << "hello: '" << format << "' failed, status "
+                       << static_cast<int>(status);
+
+  // Ensure final buffer matches expected string output
+  static const std::string expect{"HELLO, WORLD  678 0xFEEDFACE T"};
+  ASSERT_TRUE(
+      CompareFormattedStrings(expect, std::string{buffer, sizeof buffer}))
+      << "Expected '" << expect << "', got " << buffer;
+}
+
+TEST(IOApiTests, MultilineOutputTest) {
+  // Allocate buffer for multiline output
+  static constexpr int numLines{5};
+  static constexpr int lineLength{32};
+  static char buffer[numLines][lineLength];
+
+  // Create descriptor for entire buffer
+  static constexpr int staticDescriptorMaxRank{1};
+  static StaticDescriptor<staticDescriptorMaxRank> wholeStaticDescriptor;
+  static Descriptor &whole{wholeStaticDescriptor.descriptor()};
+  static SubscriptValue extent[]{numLines};
+  whole.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength, &buffer,
+      staticDescriptorMaxRank, extent, CFI_attribute_pointer);
+  whole.Dump(stderr);
+  whole.Check();
+
+  // Create descriptor for buffer section
+  static StaticDescriptor<staticDescriptorMaxRank> sectionStaticDescriptor;
+  static Descriptor &section{sectionStaticDescriptor.descriptor()};
+  static const SubscriptValue lowers[]{0}, uppers[]{4}, strides[]{1};
+  section.Establish(whole.type(), /*elementBytes=*/whole.ElementBytes(),
+      nullptr, /*maxRank=*/staticDescriptorMaxRank, extent,
+      CFI_attribute_pointer);
+
+  // Ensure C descriptor address `section.raw()` is updated without error
+  const auto error{
+      CFI_section(&section.raw(), &whole.raw(), lowers, uppers, strides)};
+  ASSERT_EQ(error, 0) << "multiline: CFI_section failed: " << error;
+  section.Dump(stderr);
+  section.Check();
+
+  // Create format string and initialize IO operation
+  const char *format{
+      "('?abcde,',T1,'>',T9,A,TL12,A,TR25,'<'//G0,17X,'abcd',1(2I4))"};
+  static auto *cookie{IONAME(BeginInternalArrayFormattedOutput)(
+      section, format, std::strlen(format))};
+
+  // Write data to buffer
+  IONAME(OutputAscii)(cookie, "WORLD", 5);
+  IONAME(OutputAscii)(cookie, "HELLO", 5);
+  IONAME(OutputInteger64)(cookie, 789);
+  for (int j{666}; j <= 999; j += 111) {
+    IONAME(OutputInteger64)(cookie, j);
+  }
+
+  // Ensure no errors occured in write operations above
+  const auto status{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(status, 0) << "multiline: '" << format << "' failed, status "
+                       << static_cast<int>(status);
+
+  static const std::string expect{">HELLO, WORLD                  <"
+                                  "                                "
+                                  "789                 abcd 666 777"
+                                  " 888 999                        "
+                                  "                                "};
+  // Ensure formatted string matches expected output
+  ASSERT_TRUE(
+      CompareFormattedStrings(expect, std::string{buffer[0], sizeof buffer}))
+      << "Expected " << expect << " but got " << buffer;
+}
+
+TEST(IOApiTests, ListInputTest) {
+  static const char input[]{",1*,(5.,6..)"};
+  static auto *cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)};
+
+  // Create real values for IO tests
+  static constexpr int numRealValues{6};
+  static float z[numRealValues];
+  for (int j{0}; j < numRealValues; ++j) {
+    z[j] = -(j + 1);
+  }
+
+  // Ensure reading complex values to floats does not result in an error
+  for (int j{0}; j < numRealValues; j += 2) {
+    ASSERT_TRUE(IONAME(InputComplex32)(cookie, &z[j]))
+        << "InputComplex32 failed with value " << z[j];
+  }
+
+  // Ensure no IO errors occured during IO operations above
+  static auto status{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(status, 0) << "Failed complex list-directed input, status "
+                       << static_cast<int>(status);
+
+  // Ensure writing complex values from floats does not result in an error
+  static constexpr int bufferSize{33};
+  static char output[bufferSize];
+  output[bufferSize - 1] = '\0';
+  cookie = IONAME(BeginInternalListOutput)(output, bufferSize - 1);
+  for (int j{0}; j < numRealValues; j += 2) {
+    ASSERT_TRUE(IONAME(OutputComplex32)(cookie, z[j], z[j + 1]))
+        << "OutputComplex32 failed when outputting value " << z[j] << ", "
+        << z[j + 1];
+  }
+
+  // Ensure no IO errors occured during IO operations above
+  status = IONAME(EndIoStatement)(cookie);
+  ASSERT_EQ(status, 0) << "Failed complex list-directed output, status "
+                       << static_cast<int>(status);
+
+  // Verify output buffer against expected value
+  static const char expect[bufferSize]{" (-1.,-2.) (-3.,-4.) (5.,6.)    "};
+  ASSERT_EQ(std::strncmp(output, expect, bufferSize), 0)
+      << "Failed complex list-directed output, expected '" << expect
+      << "', but got '" << output << "'";
+}
+
+TEST(IOApiTests, DescriptorOutputTest) {
+  static constexpr int bufferSize{9};
+  static char buffer[bufferSize];
+  static const char *format{"(2A4)"};
+  static auto *cookie{IONAME(BeginInternalFormattedOutput)(
+      buffer, bufferSize, format, std::strlen(format))};
+
+  // Create descriptor for output
+  static constexpr int staticDescriptorMaxRank{1};
+  static StaticDescriptor<staticDescriptorMaxRank> staticDescriptor;
+  static Descriptor &desc{staticDescriptor.descriptor()};
+  static constexpr int subscriptExtent{2};
+  static const SubscriptValue extent[]{subscriptExtent};
+
+  // Manually write to descriptor buffer
+  static constexpr int dataLength{4};
+  static char data[subscriptExtent][dataLength];
+  std::memcpy(data[0], "ABCD", dataLength);
+  std::memcpy(data[1], "EFGH", dataLength);
+  desc.Establish(TypeCode{CFI_type_char}, dataLength, &data,
+      staticDescriptorMaxRank, extent);
+  desc.Dump(stderr);
+  desc.Check();
+  IONAME(OutputDescriptor)(cookie, desc);
+
+  // Ensure no errors were encountered in initializing the cookie and descriptor
+  static auto formatStatus{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(formatStatus, 0)
+      << "descrOutputTest: '" << format << "' failed, status "
+      << static_cast<int>(formatStatus);
+
+  // Ensure buffer matches expected output
+  ASSERT_TRUE(
+      CompareFormattedStrings("ABCDEFGH ", std::string{buffer, sizeof buffer}));
+
+  // Begin list-directed output on cookie by descriptor
+  cookie = IONAME(BeginInternalListOutput)(buffer, sizeof buffer);
+  IONAME(OutputDescriptor)(cookie, desc);
+
+  // Ensure list-directed output does not result in an IO error
+  static auto listDirectedStatus{IONAME(EndIoStatement)(cookie)};
+  ASSERT_EQ(listDirectedStatus, 0)
+      << "descrOutputTest: list-directed failed, status "
+      << static_cast<int>(listDirectedStatus);
+
+  // Ensure buffer matches expected output
+  ASSERT_TRUE(
+      CompareFormattedStrings(" ABCDEFGH", std::string{buffer, sizeof buffer}));
+}
+
+//------------------------------------------------------------------------------
+/// Tests for output formatting real values
+//------------------------------------------------------------------------------
+
+TEST(IOApiTests, FormatZeroes) {
+  static constexpr std::pair<const char *, const char *> zeroes[]{
+      {"(E32.17,';')", "         0.00000000000000000E+00;"},
+      {"(F32.17,';')", "             0.00000000000000000;"},
+      {"(G32.17,';')", "          0.0000000000000000    ;"},
+      {"(DC,E32.17,';')", "         0,00000000000000000E+00;"},
+      {"(DC,F32.17,';')", "             0,00000000000000000;"},
+      {"(DC,G32.17,';')", "          0,0000000000000000    ;"},
+      {"(D32.17,';')", "         0.00000000000000000D+00;"},
+      {"(E32.17E1,';')", "          0.00000000000000000E+0;"},
+      {"(G32.17E1,';')", "           0.0000000000000000   ;"},
+      {"(E32.17E0,';')", "          0.00000000000000000E+0;"},
+      {"(G32.17E0,';')", "          0.0000000000000000    ;"},
+      {"(1P,E32.17,';')", "         0.00000000000000000E+00;"},
+      {"(1PE32.17,';')", "         0.00000000000000000E+00;"}, // no comma
+      {"(1P,F32.17,';')", "             0.00000000000000000;"},
+      {"(1P,G32.17,';')", "          0.0000000000000000    ;"},
+      {"(2P,E32.17,';')", "         00.0000000000000000E+00;"},
+      {"(-1P,E32.17,';')", "         0.00000000000000000E+00;"},
+      {"(G0,';')", "0.;"},
+  };
+
+  for (auto const &[format, expect] : zeroes) {
+    ASSERT_TRUE(CompareFormatReal(format, 0.0, expect))
+        << "Failed to format " << format << ", expected " << expect;
+  }
+}
+
+TEST(IOApiTests, FormatOnes) {
+  static constexpr std::pair<const char *, const char *> ones[]{
+      {"(E32.17,';')", "         0.10000000000000000E+01;"},
+      {"(F32.17,';')", "             1.00000000000000000;"},
+      {"(G32.17,';')", "          1.0000000000000000    ;"},
+      {"(E32.17E1,';')", "          0.10000000000000000E+1;"},
+      {"(G32.17E1,';')", "           1.0000000000000000   ;"},
+      {"(E32.17E0,';')", "          0.10000000000000000E+1;"},
+      {"(G32.17E0,';')", "          1.0000000000000000    ;"},
+      {"(E32.17E4,';')", "       0.10000000000000000E+0001;"},
+      {"(G32.17E4,';')", "        1.0000000000000000      ;"},
+      {"(1P,E32.17,';')", "         1.00000000000000000E+00;"},
+      {"(1PE32.17,';')", "         1.00000000000000000E+00;"}, // no comma
+      {"(1P,F32.17,';')", "            10.00000000000000000;"},
+      {"(1P,G32.17,';')", "          1.0000000000000000    ;"},
+      {"(ES32.17,';')", "         1.00000000000000000E+00;"},
+      {"(2P,E32.17,';')", "         10.0000000000000000E-01;"},
+      {"(2P,G32.17,';')", "          1.0000000000000000    ;"},
+      {"(-1P,E32.17,';')", "         0.01000000000000000E+02;"},
+      {"(-1P,G32.17,';')", "          1.0000000000000000    ;"},
+      {"(G0,';')", "1.;"},
+  };
+
+  for (auto const &[format, expect] : ones) {
+    ASSERT_TRUE(CompareFormatReal(format, 1.0, expect))
+        << "Failed to format " << format << ", expected " << expect;
+  }
+}
+
+TEST(IOApiTests, FormatNegativeOnes) {
+  static constexpr std::tuple<const char *, const char *> negOnes[]{
+      {"(E32.17,';')", "        -0.10000000000000000E+01;"},
+      {"(F32.17,';')", "            -1.00000000000000000;"},
+      {"(G32.17,';')", "         -1.0000000000000000    ;"},
+      {"(G0,';')", "-1.;"},
+  };
+  for (auto const &[format, expect] : negOnes) {
+    ASSERT_TRUE(CompareFormatReal(format, -1.0, expect))
+        << "Failed to format " << format << ", expected " << expect;
+  }
+}
+
+// Each test case contains a raw uint64, a format string for a real value, and
+// the expected resulting string from formatting the raw uint64. The double
+// representation of the uint64 is commented above each test case.
+TEST(IOApiTests, FormatDoubleValues) {
+
+  using TestCaseTy = std::tuple<std::uint64_t,
+      std::vector<std::tuple<const char *, const char *>>>;
+  static const std::vector<TestCaseTy> testCases{
+      {// -0
+          0x8000000000000000,
+          {
+              {"(E9.1,';')", " -0.0E+00;"},
+              {"(F4.0,';')", " -0.;"},
+              {"(G8.0,';')", "-0.0E+00;"},
+              {"(G8.1,';')", " -0.    ;"},
+              {"(G0,';')", "-0.;"},
+              {"(E9.1,';')", " -0.0E+00;"},
+          }},
+      {// +Inf
+          0x7ff0000000000000,
+          {
+              {"(E9.1,';')", "      Inf;"},
+              {"(F9.1,';')", "      Inf;"},
+              {"(G9.1,';')", "      Inf;"},
+              {"(SP,E9.1,';')", "     +Inf;"},
+              {"(SP,F9.1,';')", "     +Inf;"},
+              {"(SP,G9.1,';')", "     +Inf;"},
+              {"(G0,';')", "Inf;"},
+          }},
+      {// -Inf
+          0xfff0000000000000,
+          {
+              {"(E9.1,';')", "     -Inf;"},
+              {"(F9.1,';')", "     -Inf;"},
+              {"(G9.1,';')", "     -Inf;"},
+              {"(G0,';')", "-Inf;"},
+          }},
+      {// NaN
+          0x7ff0000000000001,
+          {
+              {"(E9.1,';')", "      NaN;"},
+              {"(F9.1,';')", "      NaN;"},
+              {"(G9.1,';')", "      NaN;"},
+              {"(G0,';')", "NaN;"},
+          }},
+      {// NaN (sign irrelevant)
+          0xfff0000000000001,
+          {
+              {"(E9.1,';')", "      NaN;"},
+              {"(F9.1,';')", "      NaN;"},
+              {"(G9.1,';')", "      NaN;"},
+              {"(SP,E9.1,';')", "      NaN;"},
+              {"(SP,F9.1,';')", "      NaN;"},
+              {"(SP,G9.1,';')", "      NaN;"},
+              {"(G0,';')", "NaN;"},
+          }},
+      {// 0.1 rounded
+          0x3fb999999999999a,
+          {
+              {"(E62.55,';')",
+                  " 0.1000000000000000055511151231257827021181583404541015625E+"
+                  "00;"},
+              {"(E0.0,';')", "0.E+00;"},
+              {"(E0.55,';')",
+                  "0.1000000000000000055511151231257827021181583404541015625E+"
+                  "00;"},
+              {"(E0,';')", ".1E+00;"},
+              {"(F58.55,';')",
+                  " 0."
+                  "1000000000000000055511151231257827021181583404541015625;"},
+              {"(F0.0,';')", "0.;"},
+              {"(F0.55,';')",
+                  ".1000000000000000055511151231257827021181583404541015625;"},
+              {"(F0,';')", ".1;"},
+              {"(G62.55,';')",
+                  " 0.1000000000000000055511151231257827021181583404541015625  "
+                  "  ;"},
+              {"(G0.0,';')", "0.;"},
+              {"(G0.55,';')",
+                  ".1000000000000000055511151231257827021181583404541015625;"},
+              {"(G0,';')", ".1;"},
+          }},
+      {// 1.5
+          0x3ff8000000000000,
+          {
+              {"(E9.2,';')", " 0.15E+01;"},
+              {"(F4.1,';')", " 1.5;"},
+              {"(G7.1,';')", " 2.    ;"},
+              {"(RN,E8.1,';')", " 0.2E+01;"},
+              {"(RN,F3.0,';')", " 2.;"},
+              {"(RN,G7.0,';')", " 0.E+01;"},
+              {"(RN,G7.1,';')", " 2.    ;"},
+              {"(RD,E8.1,';')", " 0.1E+01;"},
+              {"(RD,F3.0,';')", " 1.;"},
+              {"(RD,G7.0,';')", " 0.E+01;"},
+              {"(RD,G7.1,';')", " 1.    ;"},
+              {"(RU,E8.1,';')", " 0.2E+01;"},
+              {"(RU,G7.0,';')", " 0.E+01;"},
+              {"(RU,G7.1,';')", " 2.    ;"},
+              {"(RZ,E8.1,';')", " 0.1E+01;"},
+              {"(RZ,F3.0,';')", " 1.;"},
+              {"(RZ,G7.0,';')", " 0.E+01;"},
+              {"(RZ,G7.1,';')", " 1.    ;"},
+              {"(RC,E8.1,';')", " 0.2E+01;"},
+              {"(RC,F3.0,';')", " 2.;"},
+              {"(RC,G7.0,';')", " 0.E+01;"},
+              {"(RC,G7.1,';')", " 2.    ;"},
+          }},
+      {// -1.5
+          0xbff8000000000000,
+          {
+              {"(E9.2,';')", "-0.15E+01;"},
+              {"(RN,E8.1,';')", "-0.2E+01;"},
+              {"(RD,E8.1,';')", "-0.2E+01;"},
+              {"(RU,E8.1,';')", "-0.1E+01;"},
+              {"(RZ,E8.1,';')", "-0.1E+01;"},
+              {"(RC,E8.1,';')", "-0.2E+01;"},
+          }},
+      {// 2.5
+          0x4004000000000000,
+          {
+              {"(E9.2,';')", " 0.25E+01;"},
+              {"(RN,E8.1,';')", " 0.2E+01;"},
+              {"(RD,E8.1,';')", " 0.2E+01;"},
+              {"(RU,E8.1,';')", " 0.3E+01;"},
+              {"(RZ,E8.1,';')", " 0.2E+01;"},
+              {"(RC,E8.1,';')", " 0.3E+01;"},
+          }},
+      {// -2.5
+          0xc004000000000000,
+          {
+              {"(E9.2,';')", "-0.25E+01;"},
+              {"(RN,E8.1,';')", "-0.2E+01;"},
+              {"(RD,E8.1,';')", "-0.3E+01;"},
+              {"(RU,E8.1,';')", "-0.2E+01;"},
+              {"(RZ,E8.1,';')", "-0.2E+01;"},
+              {"(RC,E8.1,';')", "-0.3E+01;"},
+          }},
+      {// least positive nonzero subnormal
+          1,
+          {
+              {"(E32.17,';')", "         0.49406564584124654-323;"},
+              {"(ES32.17,';')", "         4.94065645841246544-324;"},
+              {"(EN32.17,';')", "         4.94065645841246544-324;"},
+              {"(E759.752,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "75386825064197182655334472656250-323;"},
+              {"(G0,';')", ".5-323;"},
+              {"(E757.750,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "753868250641971826553344726562-323;"},
+              {"(RN,E757.750,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "753868250641971826553344726562-323;"},
+              {"(RD,E757.750,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "753868250641971826553344726562-323;"},
+              {"(RU,E757.750,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "753868250641971826553344726563-323;"},
+              {"(RC,E757.750,';')",
+                  " 0."
+                  "494065645841246544176568792868221372365059802614324764425585"
+                  "682500675507270208751865299836361635992379796564695445717730"
+                  "926656710355939796398774796010781878126300713190311404527845"
+                  "817167848982103688718636056998730723050006387409153564984387"
+                  "312473397273169615140031715385398074126238565591171026658556"
+                  "686768187039560310624931945271591492455329305456544401127480"
+                  "129709999541931989409080416563324524757147869014726780159355"
+                  "238611550134803526493472019379026810710749170333222684475333"
+                  "572083243193609238289345836806010601150616980975307834227731"
+                  "832924790498252473077637592724787465608477820373446969953364"
+                  "701797267771758512566055119913150489110145103786273816725095"
+                  "583738973359899366480994116420570263709027924276754456522908"
+                  "753868250641971826553344726563-323;"},
+          }},
+      {// least positive nonzero normal
+          0x10000000000000,
+          {
+              {"(E723.716,';')",
+                  " 0."
+                  "222507385850720138309023271733240406421921598046233183055332"
+                  "741688720443481391819585428315901251102056406733973103581100"
+                  "515243416155346010885601238537771882113077799353200233047961"
+                  "014744258363607192156504694250373420837525080665061665815894"
+                  "872049117996859163964850063590877011830487479978088775374994"
+                  "945158045160505091539985658247081864511353793580499211598108"
+                  "576605199243335211435239014879569960959128889160299264151106"
+                  "346631339366347758651302937176204732563178148566435087212282"
+                  "863764204484681140761391147706280168985324411002416144742161"
+                  "856716615054015428508471675290190316132277889672970737312333"
+                  "408698898317506783884692609277397797285865965494109136909540"
+                  "61364675687023986783152906809846172109246253967285156250-"
+                  "307;"},
+              {"(G0,';')", ".22250738585072014-307;"},
+          }},
+      {// greatest finite
+          0x7fefffffffffffffuLL,
+          {
+              {"(E32.17,';')", "         0.17976931348623157+309;"},
+              {"(E317.310,';')",
+                  " 0."
+                  "179769313486231570814527423731704356798070567525844996598917"
+                  "476803157260780028538760589558632766878171540458953514382464"
+                  "234321326889464182768467546703537516986049910576551282076245"
+                  "490090389328944075868508455133942304583236903222948165808559"
+                  "332123348274797826204144723168738177180919299881250404026184"
+                  "1248583680+309;"},
+              {"(ES317.310,';')",
+                  " 1."
+                  "797693134862315708145274237317043567980705675258449965989174"
+                  "768031572607800285387605895586327668781715404589535143824642"
+                  "343213268894641827684675467035375169860499105765512820762454"
+                  "900903893289440758685084551339423045832369032229481658085593"
+                  "321233482747978262041447231687381771809192998812504040261841"
+                  "2485836800+308;"},
+              {"(EN319.310,';')",
+                  " 179."
+                  "769313486231570814527423731704356798070567525844996598917476"
+                  "803157260780028538760589558632766878171540458953514382464234"
+                  "321326889464182768467546703537516986049910576551282076245490"
+                  "090389328944075868508455133942304583236903222948165808559332"
+                  "123348274797826204144723168738177180919299881250404026184124"
+                  "8583680000+306;"},
+              {"(G0,';')", ".17976931348623157+309;"},
+          }},
+  };
+
+  for (auto const &[value, cases] : testCases) {
+    for (auto const &[format, expect] : cases) {
+      ASSERT_TRUE(CompareFormatReal(format, value, expect))
+          << "Failed to format " << format << ", expected " << expect;
+    }
+  }
+
+  using IndividualTestCaseTy = std::tuple<const char *, double, const char *>;
+  static std::vector<IndividualTestCaseTy> individualTestCases{
+      {"(F5.3,';')", 25., "*****;"},
+      {"(F5.3,';')", 2.5, "2.500;"},
+      {"(F5.3,';')", 0.25, "0.250;"},
+      {"(F5.3,';')", 0.025, "0.025;"},
+      {"(F5.3,';')", 0.0025, "0.003;"},
+      {"(F5.3,';')", 0.00025, "0.000;"},
+      {"(F5.3,';')", 0.000025, "0.000;"},
+      {"(F5.3,';')", -25., "*****;"},
+      {"(F5.3,';')", -2.5, "*****;"},
+      {"(F5.3,';')", -0.25, "-.250;"},
+      {"(F5.3,';')", -0.025, "-.025;"},
+      {"(F5.3,';')", -0.0025, "-.003;"},
+      {"(F5.3,';')", -0.00025, "-.000;"},
+      {"(F5.3,';')", -0.000025, "-.000;"},
+  };
+
+  for (auto const &[format, value, expect] : individualTestCases) {
+    ASSERT_TRUE(CompareFormatReal(format, value, expect))
+        << "Failed to format " << format << ", expected " << expect;
+  }
+}
+
+//------------------------------------------------------------------------------
+/// Tests for input formatting real values
+//------------------------------------------------------------------------------
+
+// Ensure double input values correctly map to raw uint64 values
+TEST(IOApiTests, FormatDoubleInputValues) {
+  using TestCaseTy = std::tuple<const char *, const char *, std::uint64_t>;
+  static std::vector<TestCaseTy> testCases{
+      {"(F18.0)", "                 0", 0x0},
+      {"(F18.0)", "                  ", 0x0},
+      {"(F18.0)", "                -0", 0x8000000000000000},
+      {"(F18.0)", "                01", 0x3ff0000000000000},
+      {"(F18.0)", "                 1", 0x3ff0000000000000},
+      {"(F18.0)", "              125.", 0x405f400000000000},
+      {"(F18.0)", "              12.5", 0x4029000000000000},
+      {"(F18.0)", "              1.25", 0x3ff4000000000000},
+      {"(F18.0)", "             01.25", 0x3ff4000000000000},
+      {"(F18.0)", "              .125", 0x3fc0000000000000},
+      {"(F18.0)", "             0.125", 0x3fc0000000000000},
+      {"(F18.0)", "             .0625", 0x3fb0000000000000},
+      {"(F18.0)", "            0.0625", 0x3fb0000000000000},
+      {"(F18.0)", "               125", 0x405f400000000000},
+      {"(F18.1)", "               125", 0x4029000000000000},
+      {"(F18.2)", "               125", 0x3ff4000000000000},
+      {"(F18.3)", "               125", 0x3fc0000000000000},
+      {"(-1P,F18.0)", "               125", 0x4093880000000000}, // 1250
+      {"(1P,F18.0)", "               125", 0x4029000000000000}, // 12.5
+      {"(BZ,F18.0)", "              125 ", 0x4093880000000000}, // 1250
+      {"(BZ,F18.0)", "       125 . e +1 ", 0x42a6bcc41e900000}, // 1.25e13
+      {"(DC,F18.0)", "              12,5", 0x4029000000000000},
+  };
+  for (auto const &[format, data, want] : testCases) {
+    auto *cookie{IONAME(BeginInternalFormattedInput)(
+        data, std::strlen(data), format, std::strlen(format))};
+    union {
+      double x;
+      std::uint64_t raw;
+    } u;
+    u.raw = 0;
+
+    // Read buffer into union value
+    IONAME(EnableHandlers)(cookie, true, true, true, true, true);
+    IONAME(InputReal64)(cookie, u.x);
+
+    static constexpr int bufferSize{65};
+    static char iomsg[bufferSize];
+    std::memset(iomsg, '\0', bufferSize - 1);
+
+    // Ensure no errors were encountered reading input buffer into union value
+    IONAME(GetIoMsg)(cookie, iomsg, bufferSize - 1);
+    static auto status{IONAME(EndIoStatement)(cookie)};
+    ASSERT_EQ(status, 0) << '\'' << format << "' failed reading '" << data
+                         << "', status " << static_cast<int>(status)
+                         << " iomsg '" << iomsg << "'";
+
+    // Ensure raw uint64 value matches expected conversion from double
+    ASSERT_EQ(u.raw, want) << '\'' << format << "' failed reading '" << data
+                           << "', want 0x" << std::hex << want << ", got 0x"
+                           << u.raw;
+  }
+}