1 //===-- flang/unittests/Runtime/NumericalFormatTest.cpp ---------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "CrashHandlerFixture.h"
10 #include "flang/Runtime/descriptor.h"
11 #include "flang/Runtime/io-api.h"
12 #include <algorithm>
13 #include <array>
14 #include <cstring>
15 #include <gtest/gtest.h>
16 #include <tuple>
17 
18 using namespace Fortran::runtime;
19 using namespace Fortran::runtime::io;
20 
CompareFormattedStrings(const std::string & expect,const std::string & got)21 static bool CompareFormattedStrings(
22     const std::string &expect, const std::string &got) {
23   std::string want{expect};
24   want.resize(got.size(), ' ');
25   return want == got;
26 }
27 
CompareFormattedStrings(const char * expect,const std::string && got)28 static bool CompareFormattedStrings(
29     const char *expect, const std::string &&got) {
30   return CompareFormattedStrings(std::string(expect), std::move(got));
31 }
32 
33 // Perform format and compare the result with expected value
CompareFormatReal(const char * format,double x,const char * expect,std::string & got)34 static bool CompareFormatReal(
35     const char *format, double x, const char *expect, std::string &got) {
36   char buffer[800];
37   auto cookie{IONAME(BeginInternalFormattedOutput)(
38       buffer, sizeof buffer, format, std::strlen(format))};
39   EXPECT_TRUE(IONAME(OutputReal64)(cookie, x));
40   auto status{IONAME(EndIoStatement)(cookie)};
41   EXPECT_EQ(status, 0);
42   got = std::string{buffer, sizeof buffer};
43   auto lastNonBlank{got.find_last_not_of(" ")};
44   if (lastNonBlank != std::string::npos) {
45     got.resize(lastNonBlank + 1);
46   }
47   return CompareFormattedStrings(expect, got);
48 }
49 
50 // Convert raw uint64 into double, perform format, and compare with expected
CompareFormatReal(const char * format,std::uint64_t xInt,const char * expect,std::string & got)51 static bool CompareFormatReal(const char *format, std::uint64_t xInt,
52     const char *expect, std::string &got) {
53   double x;
54   static_assert(sizeof(double) == sizeof(std::uint64_t),
55       "Size of double != size of uint64_t!");
56   std::memcpy(&x, &xInt, sizeof xInt);
57   return CompareFormatReal(format, x, expect, got);
58 }
59 
60 struct IOApiTests : CrashHandlerFixture {};
61 
TEST(IOApiTests,HelloWorldOutputTest)62 TEST(IOApiTests, HelloWorldOutputTest) {
63   static constexpr int bufferSize{32};
64   char buffer[bufferSize];
65 
66   // Create format for all types and values to be written
67   const char *format{"(6HHELLO,,A6,2X,I3,1X,'0x',Z8,1X,L1)"};
68   auto cookie{IONAME(BeginInternalFormattedOutput)(
69       buffer, bufferSize, format, std::strlen(format))};
70 
71   // Write string, integer, and logical values to buffer
72   IONAME(OutputAscii)(cookie, "WORLD", 5);
73   IONAME(OutputInteger64)(cookie, 678);
74   IONAME(OutputInteger32)(cookie, 0xfeedface);
75   IONAME(OutputLogical)(cookie, true);
76 
77   // Ensure IO succeeded
78   auto status{IONAME(EndIoStatement)(cookie)};
79   ASSERT_EQ(status, 0) << "hello: '" << format << "' failed, status "
80                        << static_cast<int>(status);
81 
82   // Ensure final buffer matches expected string output
83   static const std::string expect{"HELLO, WORLD  678 0xFEEDFACE T"};
84   ASSERT_TRUE(
85       CompareFormattedStrings(expect, std::string{buffer, sizeof buffer}))
86       << "Expected '" << expect << "', got " << buffer;
87 }
88 
TEST(IOApiTests,MultilineOutputTest)89 TEST(IOApiTests, MultilineOutputTest) {
90   // Allocate buffer for multiline output
91   static constexpr int numLines{5};
92   static constexpr int lineLength{32};
93   char buffer[numLines][lineLength];
94 
95   // Create descriptor for entire buffer
96   static constexpr int staticDescriptorMaxRank{1};
97   StaticDescriptor<staticDescriptorMaxRank> wholeStaticDescriptor;
98   Descriptor &whole{wholeStaticDescriptor.descriptor()};
99   static const SubscriptValue extent[]{numLines};
100   whole.Establish(TypeCode{CFI_type_char}, /*elementBytes=*/lineLength, &buffer,
101       staticDescriptorMaxRank, extent, CFI_attribute_pointer);
102   whole.Dump(stderr);
103   whole.Check();
104 
105   // Create descriptor for buffer section
106   StaticDescriptor<staticDescriptorMaxRank> sectionStaticDescriptor;
107   Descriptor &section{sectionStaticDescriptor.descriptor()};
108   static const SubscriptValue lowers[]{0}, uppers[]{4}, strides[]{1};
109   section.Establish(whole.type(), /*elementBytes=*/whole.ElementBytes(),
110       nullptr, /*maxRank=*/staticDescriptorMaxRank, extent,
111       CFI_attribute_pointer);
112 
113   // Ensure C descriptor address `section.raw()` is updated without error
114   const auto error{
115       CFI_section(&section.raw(), &whole.raw(), lowers, uppers, strides)};
116   ASSERT_EQ(error, 0) << "multiline: CFI_section failed: " << error;
117   section.Dump(stderr);
118   section.Check();
119 
120   // Create format string and initialize IO operation
121   const char *format{
122       "('?abcde,',T1,'>',T9,A,TL12,A,TR25,'<'//G0,17X,'abcd',1(2I4))"};
123   auto cookie{IONAME(BeginInternalArrayFormattedOutput)(
124       section, format, std::strlen(format))};
125 
126   // Fill last line with periods
127   std::memset(buffer[numLines - 1], '.', lineLength);
128 
129   // Write data to buffer
130   IONAME(OutputAscii)(cookie, "WORLD", 5);
131   IONAME(OutputAscii)(cookie, "HELLO", 5);
132   IONAME(OutputInteger64)(cookie, 789);
133   for (int j{666}; j <= 999; j += 111) {
134     IONAME(OutputInteger64)(cookie, j);
135   }
136 
137   // Ensure no errors occured in write operations above
138   const auto status{IONAME(EndIoStatement)(cookie)};
139   ASSERT_EQ(status, 0) << "multiline: '" << format << "' failed, status "
140                        << static_cast<int>(status);
141 
142   static const std::string expect{">HELLO, WORLD                  <"
143                                   "                                "
144                                   "789                 abcd 666 777"
145                                   " 888 999                        "
146                                   "................................"};
147   // Ensure formatted string matches expected output
148   EXPECT_TRUE(
149       CompareFormattedStrings(expect, std::string{buffer[0], sizeof buffer}))
150       << "Expected '" << expect << "' but got '"
151       << std::string{buffer[0], sizeof buffer} << "'";
152 }
153 
TEST(IOApiTests,ListInputTest)154 TEST(IOApiTests, ListInputTest) {
155   static const char input[]{",1*,(5.,6.),(7.0,8.0)"};
156   auto cookie{IONAME(BeginInternalListInput)(input, sizeof input - 1)};
157 
158   // Create real values for IO tests
159   static constexpr int numRealValues{8};
160   float z[numRealValues];
161   for (int j{0}; j < numRealValues; ++j) {
162     z[j] = -(j + 1);
163   }
164 
165   // Ensure reading complex values to floats does not result in an error
166   for (int j{0}; j < numRealValues; j += 2) {
167     ASSERT_TRUE(IONAME(InputComplex32)(cookie, &z[j]))
168         << "InputComplex32 failed with value " << z[j];
169   }
170 
171   // Ensure no IO errors occured during IO operations above
172   auto status{IONAME(EndIoStatement)(cookie)};
173   ASSERT_EQ(status, 0) << "Failed complex list-directed input, status "
174                        << static_cast<int>(status);
175 
176   // Ensure writing complex values from floats does not result in an error
177   static constexpr int bufferSize{39};
178   char output[bufferSize];
179   output[bufferSize - 1] = '\0';
180   cookie = IONAME(BeginInternalListOutput)(output, bufferSize - 1);
181   for (int j{0}; j < numRealValues; j += 2) {
182     ASSERT_TRUE(IONAME(OutputComplex32)(cookie, z[j], z[j + 1]))
183         << "OutputComplex32 failed when outputting value " << z[j] << ", "
184         << z[j + 1];
185   }
186 
187   // Ensure no IO errors occured during IO operations above
188   status = IONAME(EndIoStatement)(cookie);
189   ASSERT_EQ(status, 0) << "Failed complex list-directed output, status "
190                        << static_cast<int>(status);
191 
192   // Verify output buffer against expected value
193   static const char expect[bufferSize]{
194       " (-1.,-2.) (-3.,-4.) (5.,6.) (7.,8.)  "};
195   ASSERT_EQ(std::strncmp(output, expect, bufferSize), 0)
196       << "Failed complex list-directed output, expected '" << expect
197       << "', but got '" << output << "'";
198 }
199 
TEST(IOApiTests,DescriptorOutputTest)200 TEST(IOApiTests, DescriptorOutputTest) {
201   static constexpr int bufferSize{10};
202   char buffer[bufferSize];
203   const char *format{"(2A4)"};
204   auto cookie{IONAME(BeginInternalFormattedOutput)(
205       buffer, bufferSize, format, std::strlen(format))};
206 
207   // Create descriptor for output
208   static constexpr int staticDescriptorMaxRank{1};
209   StaticDescriptor<staticDescriptorMaxRank> staticDescriptor;
210   Descriptor &desc{staticDescriptor.descriptor()};
211   static constexpr int subscriptExtent{2};
212   static const SubscriptValue extent[]{subscriptExtent};
213 
214   // Manually write to descriptor buffer
215   static constexpr int dataLength{4};
216   char data[subscriptExtent][dataLength];
217   std::memcpy(data[0], "ABCD", dataLength);
218   std::memcpy(data[1], "EFGH", dataLength);
219   desc.Establish(TypeCode{CFI_type_char}, dataLength, &data,
220       staticDescriptorMaxRank, extent);
221   desc.Dump(stderr);
222   desc.Check();
223   IONAME(OutputDescriptor)(cookie, desc);
224 
225   // Ensure no errors were encountered in initializing the cookie and descriptor
226   auto formatStatus{IONAME(EndIoStatement)(cookie)};
227   ASSERT_EQ(formatStatus, 0)
228       << "descrOutputTest: '" << format << "' failed, status "
229       << static_cast<int>(formatStatus);
230 
231   // Ensure buffer matches expected output
232   EXPECT_TRUE(
233       CompareFormattedStrings("ABCDEFGH  ", std::string{buffer, sizeof buffer}))
234       << "descrOutputTest: formatted: got '"
235       << std::string{buffer, sizeof buffer} << "'";
236 
237   // Begin list-directed output on cookie by descriptor
238   cookie = IONAME(BeginInternalListOutput)(buffer, sizeof buffer);
239   IONAME(OutputDescriptor)(cookie, desc);
240 
241   // Ensure list-directed output does not result in an IO error
242   auto listDirectedStatus{IONAME(EndIoStatement)(cookie)};
243   ASSERT_EQ(listDirectedStatus, 0)
244       << "descrOutputTest: list-directed failed, status "
245       << static_cast<int>(listDirectedStatus);
246 
247   // Ensure buffer matches expected output
248   EXPECT_TRUE(
249       CompareFormattedStrings(" ABCDEFGH ", std::string{buffer, sizeof buffer}))
250       << "descrOutputTest: list-directed: got '"
251       << std::string{buffer, sizeof buffer} << "'";
252 }
253 
254 //------------------------------------------------------------------------------
255 /// Tests for output formatting real values
256 //------------------------------------------------------------------------------
257 
TEST(IOApiTests,FormatZeroes)258 TEST(IOApiTests, FormatZeroes) {
259   static constexpr std::pair<const char *, const char *> zeroes[]{
260       {"(E32.17,';')", "         0.00000000000000000E+00;"},
261       {"(F32.17,';')", "             0.00000000000000000;"},
262       {"(G32.17,';')", "          0.0000000000000000    ;"},
263       {"(DC,E32.17,';')", "         0,00000000000000000E+00;"},
264       {"(DC,F32.17,';')", "             0,00000000000000000;"},
265       {"(DC,G32.17,';')", "          0,0000000000000000    ;"},
266       {"(D32.17,';')", "         0.00000000000000000D+00;"},
267       {"(E32.17E1,';')", "          0.00000000000000000E+0;"},
268       {"(G32.17E1,';')", "           0.0000000000000000   ;"},
269       {"(E32.17E0,';')", "          0.00000000000000000E+0;"},
270       {"(G32.17E0,';')", "          0.0000000000000000    ;"},
271       {"(1P,E32.17,';')", "         0.00000000000000000E+00;"},
272       {"(1PE32.17,';')", "         0.00000000000000000E+00;"}, // no comma
273       {"(1P,F32.17,';')", "             0.00000000000000000;"},
274       {"(1P,G32.17,';')", "          0.0000000000000000    ;"},
275       {"(2P,E32.17,';')", "         00.0000000000000000E+00;"},
276       {"(-1P,E32.17,';')", "         0.00000000000000000E+00;"},
277       {"(G0,';')", "0.;"},
278   };
279 
280   for (auto const &[format, expect] : zeroes) {
281     std::string got;
282     ASSERT_TRUE(CompareFormatReal(format, 0.0, expect, got))
283         << "Failed to format " << format << ", expected '" << expect
284         << "', got '" << got << "'";
285   }
286 }
287 
TEST(IOApiTests,FormatOnes)288 TEST(IOApiTests, FormatOnes) {
289   static constexpr std::pair<const char *, const char *> ones[]{
290       {"(E32.17,';')", "         0.10000000000000000E+01;"},
291       {"(F32.17,';')", "             1.00000000000000000;"},
292       {"(G32.17,';')", "          1.0000000000000000    ;"},
293       {"(E32.17E1,';')", "          0.10000000000000000E+1;"},
294       {"(G32.17E1,';')", "           1.0000000000000000   ;"},
295       {"(E32.17E0,';')", "          0.10000000000000000E+1;"},
296       {"(G32.17E0,';')", "          1.0000000000000000    ;"},
297       {"(E32.17E4,';')", "       0.10000000000000000E+0001;"},
298       {"(G32.17E4,';')", "        1.0000000000000000      ;"},
299       {"(1P,E32.17,';')", "         1.00000000000000000E+00;"},
300       {"(1PE32.17,';')", "         1.00000000000000000E+00;"}, // no comma
301       {"(1P,F32.17,';')", "            10.00000000000000000;"},
302       {"(1P,G32.17,';')", "          1.0000000000000000    ;"},
303       {"(ES32.17,';')", "         1.00000000000000000E+00;"},
304       {"(2P,E32.17,';')", "         10.0000000000000000E-01;"},
305       {"(2P,G32.17,';')", "          1.0000000000000000    ;"},
306       {"(-1P,E32.17,';')", "         0.01000000000000000E+02;"},
307       {"(-1P,G32.17,';')", "          1.0000000000000000    ;"},
308       {"(G0,';')", "1.;"},
309   };
310 
311   for (auto const &[format, expect] : ones) {
312     std::string got;
313     ASSERT_TRUE(CompareFormatReal(format, 1.0, expect, got))
314         << "Failed to format " << format << ", expected '" << expect
315         << "', got '" << got << "'";
316   }
317 }
318 
TEST(IOApiTests,FormatNegativeOnes)319 TEST(IOApiTests, FormatNegativeOnes) {
320   static constexpr std::tuple<const char *, const char *> negOnes[]{
321       {"(E32.17,';')", "        -0.10000000000000000E+01;"},
322       {"(F32.17,';')", "            -1.00000000000000000;"},
323       {"(G32.17,';')", "         -1.0000000000000000    ;"},
324       {"(G0,';')", "-1.;"},
325   };
326   for (auto const &[format, expect] : negOnes) {
327     std::string got;
328     ASSERT_TRUE(CompareFormatReal(format, -1.0, expect, got))
329         << "Failed to format " << format << ", expected '" << expect
330         << "', got '" << got << "'";
331   }
332 }
333 
334 // Each test case contains a raw uint64, a format string for a real value, and
335 // the expected resulting string from formatting the raw uint64. The double
336 // representation of the uint64 is commented above each test case.
TEST(IOApiTests,FormatDoubleValues)337 TEST(IOApiTests, FormatDoubleValues) {
338 
339   using TestCaseTy = std::tuple<std::uint64_t,
340       std::vector<std::tuple<const char *, const char *>>>;
341   static const std::vector<TestCaseTy> testCases{
342       {// -0
343           0x8000000000000000,
344           {
345               {"(E9.1,';')", " -0.0E+00;"},
346               {"(F4.0,';')", " -0.;"},
347               {"(F0.1,';')", "-.0;"},
348               {"(G8.0,';')", "-0.0E+00;"},
349               {"(G8.1,';')", " -0.    ;"},
350               {"(G0,';')", "-0.;"},
351               {"(E9.1,';')", " -0.0E+00;"},
352           }},
353       {// +Inf
354           0x7ff0000000000000,
355           {
356               {"(E9.1,';')", "      Inf;"},
357               {"(F9.1,';')", "      Inf;"},
358               {"(G9.1,';')", "      Inf;"},
359               {"(SP,E9.1,';')", "     +Inf;"},
360               {"(SP,F9.1,';')", "     +Inf;"},
361               {"(SP,G9.1,';')", "     +Inf;"},
362               {"(G0,';')", "Inf;"},
363           }},
364       {// -Inf
365           0xfff0000000000000,
366           {
367               {"(E9.1,';')", "     -Inf;"},
368               {"(F9.1,';')", "     -Inf;"},
369               {"(G9.1,';')", "     -Inf;"},
370               {"(G0,';')", "-Inf;"},
371           }},
372       {// NaN
373           0x7ff0000000000001,
374           {
375               {"(E9.1,';')", "      NaN;"},
376               {"(F9.1,';')", "      NaN;"},
377               {"(G9.1,';')", "      NaN;"},
378               {"(G0,';')", "NaN;"},
379           }},
380       {// NaN (sign irrelevant)
381           0xfff0000000000001,
382           {
383               {"(E9.1,';')", "      NaN;"},
384               {"(F9.1,';')", "      NaN;"},
385               {"(G9.1,';')", "      NaN;"},
386               {"(SP,E9.1,';')", "      NaN;"},
387               {"(SP,F9.1,';')", "      NaN;"},
388               {"(SP,G9.1,';')", "      NaN;"},
389               {"(G0,';')", "NaN;"},
390           }},
391       {// 0.1 rounded
392           0x3fb999999999999a,
393           {
394               {"(E62.55,';')",
395                   " 0.1000000000000000055511151231257827021181583404541015625E+"
396                   "00;"},
397               {"(E0.0,';')", ".1E+00;"},
398               {"(E0.55,';')",
399                   ".1000000000000000055511151231257827021181583404541015625E+"
400                   "00;"},
401               {"(E0,';')", ".1E+00;"},
402               {"(F58.55,';')",
403                   " 0."
404                   "1000000000000000055511151231257827021181583404541015625;"},
405               {"(F0.0,';')", "0.;"},
406               {"(F0.55,';')",
407                   ".1000000000000000055511151231257827021181583404541015625;"},
408               {"(F0,';')", ".1;"},
409               {"(G62.55,';')",
410                   " 0.1000000000000000055511151231257827021181583404541015625  "
411                   "  ;"},
412               {"(G0.0,';')", "0.;"},
413               {"(G0.55,';')",
414                   ".1000000000000000055511151231257827021181583404541015625;"},
415               {"(G0,';')", ".1;"},
416           }},
417       {// 1.5
418           0x3ff8000000000000,
419           {
420               {"(E9.2,';')", " 0.15E+01;"},
421               {"(F4.1,';')", " 1.5;"},
422               {"(G7.1,';')", " 2.    ;"},
423               {"(RN,E8.1,';')", " 0.2E+01;"},
424               {"(RN,F3.0,';')", " 2.;"},
425               {"(RN,G7.0,';')", " 0.E+01;"},
426               {"(RN,G7.1,';')", " 2.    ;"},
427               {"(RD,E8.1,';')", " 0.1E+01;"},
428               {"(RD,F3.0,';')", " 1.;"},
429               {"(RD,G7.0,';')", " 0.E+01;"},
430               {"(RD,G7.1,';')", " 1.    ;"},
431               {"(RU,E8.1,';')", " 0.2E+01;"},
432               {"(RU,G7.0,';')", " 0.E+01;"},
433               {"(RU,G7.1,';')", " 2.    ;"},
434               {"(RZ,E8.1,';')", " 0.1E+01;"},
435               {"(RZ,F3.0,';')", " 1.;"},
436               {"(RZ,G7.0,';')", " 0.E+01;"},
437               {"(RZ,G7.1,';')", " 1.    ;"},
438               {"(RC,E8.1,';')", " 0.2E+01;"},
439               {"(RC,F3.0,';')", " 2.;"},
440               {"(RC,G7.0,';')", " 0.E+01;"},
441               {"(RC,G7.1,';')", " 2.    ;"},
442           }},
443       {// -1.5
444           0xbff8000000000000,
445           {
446               {"(E9.2,';')", "-0.15E+01;"},
447               {"(RN,E8.1,';')", "-0.2E+01;"},
448               {"(RD,E8.1,';')", "-0.2E+01;"},
449               {"(RU,E8.1,';')", "-0.1E+01;"},
450               {"(RZ,E8.1,';')", "-0.1E+01;"},
451               {"(RC,E8.1,';')", "-0.2E+01;"},
452           }},
453       {// 2.5
454           0x4004000000000000,
455           {
456               {"(E9.2,';')", " 0.25E+01;"},
457               {"(RN,E8.1,';')", " 0.2E+01;"},
458               {"(RD,E8.1,';')", " 0.2E+01;"},
459               {"(RU,E8.1,';')", " 0.3E+01;"},
460               {"(RZ,E8.1,';')", " 0.2E+01;"},
461               {"(RC,E8.1,';')", " 0.3E+01;"},
462           }},
463       {// -2.5
464           0xc004000000000000,
465           {
466               {"(E9.2,';')", "-0.25E+01;"},
467               {"(RN,E8.1,';')", "-0.2E+01;"},
468               {"(RD,E8.1,';')", "-0.3E+01;"},
469               {"(RU,E8.1,';')", "-0.2E+01;"},
470               {"(RZ,E8.1,';')", "-0.2E+01;"},
471               {"(RC,E8.1,';')", "-0.3E+01;"},
472           }},
473       {// least positive nonzero subnormal
474           1,
475           {
476               {"(E32.17,';')", "         0.49406564584124654-323;"},
477               {"(ES32.17,';')", "         4.94065645841246544-324;"},
478               {"(EN32.17,';')", "         4.94065645841246544-324;"},
479               {"(E759.752,';')",
480                   " 0."
481                   "494065645841246544176568792868221372365059802614324764425585"
482                   "682500675507270208751865299836361635992379796564695445717730"
483                   "926656710355939796398774796010781878126300713190311404527845"
484                   "817167848982103688718636056998730723050006387409153564984387"
485                   "312473397273169615140031715385398074126238565591171026658556"
486                   "686768187039560310624931945271591492455329305456544401127480"
487                   "129709999541931989409080416563324524757147869014726780159355"
488                   "238611550134803526493472019379026810710749170333222684475333"
489                   "572083243193609238289345836806010601150616980975307834227731"
490                   "832924790498252473077637592724787465608477820373446969953364"
491                   "701797267771758512566055119913150489110145103786273816725095"
492                   "583738973359899366480994116420570263709027924276754456522908"
493                   "75386825064197182655334472656250-323;"},
494               {"(G0,';')", ".5E-323;"},
495               {"(E757.750,';')",
496                   " 0."
497                   "494065645841246544176568792868221372365059802614324764425585"
498                   "682500675507270208751865299836361635992379796564695445717730"
499                   "926656710355939796398774796010781878126300713190311404527845"
500                   "817167848982103688718636056998730723050006387409153564984387"
501                   "312473397273169615140031715385398074126238565591171026658556"
502                   "686768187039560310624931945271591492455329305456544401127480"
503                   "129709999541931989409080416563324524757147869014726780159355"
504                   "238611550134803526493472019379026810710749170333222684475333"
505                   "572083243193609238289345836806010601150616980975307834227731"
506                   "832924790498252473077637592724787465608477820373446969953364"
507                   "701797267771758512566055119913150489110145103786273816725095"
508                   "583738973359899366480994116420570263709027924276754456522908"
509                   "753868250641971826553344726562-323;"},
510               {"(RN,E757.750,';')",
511                   " 0."
512                   "494065645841246544176568792868221372365059802614324764425585"
513                   "682500675507270208751865299836361635992379796564695445717730"
514                   "926656710355939796398774796010781878126300713190311404527845"
515                   "817167848982103688718636056998730723050006387409153564984387"
516                   "312473397273169615140031715385398074126238565591171026658556"
517                   "686768187039560310624931945271591492455329305456544401127480"
518                   "129709999541931989409080416563324524757147869014726780159355"
519                   "238611550134803526493472019379026810710749170333222684475333"
520                   "572083243193609238289345836806010601150616980975307834227731"
521                   "832924790498252473077637592724787465608477820373446969953364"
522                   "701797267771758512566055119913150489110145103786273816725095"
523                   "583738973359899366480994116420570263709027924276754456522908"
524                   "753868250641971826553344726562-323;"},
525               {"(RD,E757.750,';')",
526                   " 0."
527                   "494065645841246544176568792868221372365059802614324764425585"
528                   "682500675507270208751865299836361635992379796564695445717730"
529                   "926656710355939796398774796010781878126300713190311404527845"
530                   "817167848982103688718636056998730723050006387409153564984387"
531                   "312473397273169615140031715385398074126238565591171026658556"
532                   "686768187039560310624931945271591492455329305456544401127480"
533                   "129709999541931989409080416563324524757147869014726780159355"
534                   "238611550134803526493472019379026810710749170333222684475333"
535                   "572083243193609238289345836806010601150616980975307834227731"
536                   "832924790498252473077637592724787465608477820373446969953364"
537                   "701797267771758512566055119913150489110145103786273816725095"
538                   "583738973359899366480994116420570263709027924276754456522908"
539                   "753868250641971826553344726562-323;"},
540               {"(RU,E757.750,';')",
541                   " 0."
542                   "494065645841246544176568792868221372365059802614324764425585"
543                   "682500675507270208751865299836361635992379796564695445717730"
544                   "926656710355939796398774796010781878126300713190311404527845"
545                   "817167848982103688718636056998730723050006387409153564984387"
546                   "312473397273169615140031715385398074126238565591171026658556"
547                   "686768187039560310624931945271591492455329305456544401127480"
548                   "129709999541931989409080416563324524757147869014726780159355"
549                   "238611550134803526493472019379026810710749170333222684475333"
550                   "572083243193609238289345836806010601150616980975307834227731"
551                   "832924790498252473077637592724787465608477820373446969953364"
552                   "701797267771758512566055119913150489110145103786273816725095"
553                   "583738973359899366480994116420570263709027924276754456522908"
554                   "753868250641971826553344726563-323;"},
555               {"(RC,E757.750,';')",
556                   " 0."
557                   "494065645841246544176568792868221372365059802614324764425585"
558                   "682500675507270208751865299836361635992379796564695445717730"
559                   "926656710355939796398774796010781878126300713190311404527845"
560                   "817167848982103688718636056998730723050006387409153564984387"
561                   "312473397273169615140031715385398074126238565591171026658556"
562                   "686768187039560310624931945271591492455329305456544401127480"
563                   "129709999541931989409080416563324524757147869014726780159355"
564                   "238611550134803526493472019379026810710749170333222684475333"
565                   "572083243193609238289345836806010601150616980975307834227731"
566                   "832924790498252473077637592724787465608477820373446969953364"
567                   "701797267771758512566055119913150489110145103786273816725095"
568                   "583738973359899366480994116420570263709027924276754456522908"
569                   "753868250641971826553344726563-323;"},
570           }},
571       {// least positive nonzero normal
572           0x10000000000000,
573           {
574               {"(E723.716,';')",
575                   " 0."
576                   "222507385850720138309023271733240406421921598046233183055332"
577                   "741688720443481391819585428315901251102056406733973103581100"
578                   "515243416155346010885601238537771882113077799353200233047961"
579                   "014744258363607192156504694250373420837525080665061665815894"
580                   "872049117996859163964850063590877011830487479978088775374994"
581                   "945158045160505091539985658247081864511353793580499211598108"
582                   "576605199243335211435239014879569960959128889160299264151106"
583                   "346631339366347758651302937176204732563178148566435087212282"
584                   "863764204484681140761391147706280168985324411002416144742161"
585                   "856716615054015428508471675290190316132277889672970737312333"
586                   "408698898317506783884692609277397797285865965494109136909540"
587                   "61364675687023986783152906809846172109246253967285156250-"
588                   "307;"},
589               {"(G0,';')", ".22250738585072014E-307;"},
590           }},
591       {// greatest finite
592           0x7fefffffffffffffuLL,
593           {
594               {"(E32.17,';')", "         0.17976931348623157+309;"},
595               {"(E317.310,';')",
596                   " 0."
597                   "179769313486231570814527423731704356798070567525844996598917"
598                   "476803157260780028538760589558632766878171540458953514382464"
599                   "234321326889464182768467546703537516986049910576551282076245"
600                   "490090389328944075868508455133942304583236903222948165808559"
601                   "332123348274797826204144723168738177180919299881250404026184"
602                   "1248583680+309;"},
603               {"(ES317.310,';')",
604                   " 1."
605                   "797693134862315708145274237317043567980705675258449965989174"
606                   "768031572607800285387605895586327668781715404589535143824642"
607                   "343213268894641827684675467035375169860499105765512820762454"
608                   "900903893289440758685084551339423045832369032229481658085593"
609                   "321233482747978262041447231687381771809192998812504040261841"
610                   "2485836800+308;"},
611               {"(EN319.310,';')",
612                   " 179."
613                   "769313486231570814527423731704356798070567525844996598917476"
614                   "803157260780028538760589558632766878171540458953514382464234"
615                   "321326889464182768467546703537516986049910576551282076245490"
616                   "090389328944075868508455133942304583236903222948165808559332"
617                   "123348274797826204144723168738177180919299881250404026184124"
618                   "8583680000+306;"},
619               {"(G0,';')", ".17976931348623157E+309;"},
620           }},
621   };
622 
623   for (auto const &[value, cases] : testCases) {
624     for (auto const &[format, expect] : cases) {
625       std::string got;
626       ASSERT_TRUE(CompareFormatReal(format, value, expect, got))
627           << "Failed to format " << format << ", expected '" << expect
628           << "', got '" << got << "'";
629     }
630   }
631 
632   using IndividualTestCaseTy = std::tuple<const char *, double, const char *>;
633   static const std::vector<IndividualTestCaseTy> individualTestCases{
634       {"(F5.3,';')", 25., "*****;"},
635       {"(F5.3,';')", 2.5, "2.500;"},
636       {"(F5.3,';')", 0.25, "0.250;"},
637       {"(F5.3,';')", 0.025, "0.025;"},
638       {"(F5.3,';')", 0.0025, "0.003;"},
639       {"(F5.3,';')", 0.00025, "0.000;"},
640       {"(F5.3,';')", 0.000025, "0.000;"},
641       {"(F5.3,';')", -25., "*****;"},
642       {"(F5.3,';')", -2.5, "*****;"},
643       {"(F5.3,';')", -0.25, "-.250;"},
644       {"(F5.3,';')", -0.025, "-.025;"},
645       {"(F5.3,';')", -0.0025, "-.003;"},
646       {"(F5.3,';')", -0.00025, "-.000;"},
647       {"(F5.3,';')", -0.000025, "-.000;"},
648       {"(F5.3,';')", 99.999, "*****;"},
649       {"(F5.3,';')", 9.9999, "*****;"},
650       {"(F5.3,';')", 0.99999, "1.000;"},
651       {"(F5.3,';')", 0.099999, "0.100;"},
652       {"(F5.3,';')", 0.0099999, "0.010;"},
653       {"(F5.3,';')", 0.00099999, "0.001;"},
654       {"(F5.3,';')", 0.0005, "0.001;"},
655       {"(F5.3,';')", 0.00049999, "0.000;"},
656       {"(F5.3,';')", 0.000099999, "0.000;"},
657       {"(F5.3,';')", -99.999, "*****;"},
658       {"(F5.3,';')", -9.9999, "*****;"},
659       {"(F5.3,';')", -0.99999, "*****;"},
660       {"(F5.3,';')", -0.099999, "-.100;"},
661       {"(F5.3,';')", -0.0099999, "-.010;"},
662       {"(F5.3,';')", -0.00099999, "-.001;"},
663       {"(F5.3,';')", -0.0005, "-.001;"},
664       {"(F5.3,';')", -0.00049999, "-.000;"},
665       {"(F5.3,';')", -0.000099999, "-.000;"},
666       {"(F0.1,';')", 0.0, ".0;"},
667   };
668 
669   for (auto const &[format, value, expect] : individualTestCases) {
670     std::string got;
671     ASSERT_TRUE(CompareFormatReal(format, value, expect, got))
672         << "Failed to format " << format << ", expected '" << expect
673         << "', got '" << got << "'";
674   }
675 
676   // Problematic EN formatting edge cases with rounding
677   using IndividualENTestCaseTy = std::tuple<std::uint64_t, const char *>;
678   static const std::vector<IndividualENTestCaseTy> individualENTestCases{
679       {0x3E11183197785F8C, " 995.0E-12"}, // 0.9950312500000000115852E-09
680       {0x3E11180E68455D30, " 995.0E-12"}, // 0.9949999999999999761502E-09
681       {0x3E112BD8F4F6B0D7, " 999.5E-12"}, // 0.9994999999999999089118E-09
682       {0x3E45794883CA8782, "  10.0E-09"}, // 0.9999499999999999642266E-08
683       {0x3F506218230C7482, " 999.9E-06"}, // 0.9999499999999998840761E-03
684       {0x3FB99652BD3C3612, " 100.0E-03"}, // 0.9999500000000000055067E+00
685       {0x4023E66666666667, "  10.0E+00"}, // 0.9950000000000001065814E+01
686   };
687 
688   for (auto const &[value, expect] : individualENTestCases) {
689     std::string got;
690     ASSERT_TRUE(CompareFormatReal("(EN10.1)", value, expect, got))
691         << "Failed to format EN10.1, expected '" << expect << "', got '" << got
692         << "'";
693   }
694 }
695 
696 //------------------------------------------------------------------------------
697 /// Tests for input formatting real values
698 //------------------------------------------------------------------------------
699 
700 // Ensure double input values correctly map to raw uint64 values
TEST(IOApiTests,FormatDoubleInputValues)701 TEST(IOApiTests, FormatDoubleInputValues) {
702   using TestCaseTy = std::tuple<const char *, const char *, std::uint64_t>;
703   static const std::vector<TestCaseTy> testCases{
704       {"(F18.0)", "                 0", 0x0},
705       {"(F18.0)", "                  ", 0x0},
706       {"(F18.0)", "                -0", 0x8000000000000000},
707       {"(F18.0)", "                01", 0x3ff0000000000000},
708       {"(F18.0)", "                 1", 0x3ff0000000000000},
709       {"(F18.0)", "              125.", 0x405f400000000000},
710       {"(F18.0)", "              12.5", 0x4029000000000000},
711       {"(F18.0)", "              1.25", 0x3ff4000000000000},
712       {"(F18.0)", "             01.25", 0x3ff4000000000000},
713       {"(F18.0)", "              .125", 0x3fc0000000000000},
714       {"(F18.0)", "             0.125", 0x3fc0000000000000},
715       {"(F18.0)", "             .0625", 0x3fb0000000000000},
716       {"(F18.0)", "            0.0625", 0x3fb0000000000000},
717       {"(F18.0)", "               125", 0x405f400000000000},
718       {"(F18.1)", "               125", 0x4029000000000000},
719       {"(F18.2)", "               125", 0x3ff4000000000000},
720       {"(F18.3)", "               125", 0x3fc0000000000000},
721       {"(-1P,F18.0)", "               125", 0x4093880000000000}, // 1250
722       {"(1P,F18.0)", "               125", 0x4029000000000000}, // 12.5
723       {"(BZ,F18.0)", "              125 ", 0x4093880000000000}, // 1250
724       {"(BZ,F18.0)", "       125 . e +1 ", 0x42a6bcc41e900000}, // 1.25e13
725       {"(BZ,F18.0)", "           .      ", 0x0},
726       {"(BZ,F18.0)", "           . e +1 ", 0x0},
727       {"(DC,F18.0)", "              12,5", 0x4029000000000000},
728   };
729   for (auto const &[format, data, want] : testCases) {
730     auto cookie{IONAME(BeginInternalFormattedInput)(
731         data, std::strlen(data), format, std::strlen(format))};
732     union {
733       double x;
734       std::uint64_t raw;
735     } u;
736     u.raw = 0;
737 
738     // Read buffer into union value
739     IONAME(EnableHandlers)(cookie, true, true, true, true, true);
740     IONAME(InputReal64)(cookie, u.x);
741 
742     static constexpr int bufferSize{65};
743     char iomsg[bufferSize];
744     std::memset(iomsg, '\0', bufferSize - 1);
745 
746     // Ensure no errors were encountered reading input buffer into union value
747     IONAME(GetIoMsg)(cookie, iomsg, bufferSize - 1);
748     auto status{IONAME(EndIoStatement)(cookie)};
749     ASSERT_EQ(status, 0) << '\'' << format << "' failed reading '" << data
750                          << "', status " << static_cast<int>(status)
751                          << " iomsg '" << iomsg << "'";
752 
753     // Ensure raw uint64 value matches expected conversion from double
754     ASSERT_EQ(u.raw, want) << '\'' << format << "' failed reading '" << data
755                            << "', want " << want << ", got " << u.raw;
756   }
757 }
758