Skip to content

Commit d1b09ad

Browse files
committed
[flang] Fix rounding edge case in F output editing
When an Fw.d output edit descriptor has a "d" value exactly equal to the number of zeroes after the decimal point for a value (e.g., 0.07 with F5.1), the Fw.d output editing code needs to do the rounding itself to either 0.0 or 0.1 after performing a conversion without rounding (to avoid 0.04999 rounding up twice). Differential Revision: https://reviews.llvm.org/D113698
1 parent f46f93b commit d1b09ad

File tree

3 files changed

+47
-15
lines changed

3 files changed

+47
-15
lines changed

flang/runtime/edit-output.cpp

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,10 @@ bool RealOutputEditingBase::EmitSuffix(const DataEdit &edit) {
159159

160160
template <int binaryPrecision>
161161
decimal::ConversionToDecimalResult RealOutputEditing<binaryPrecision>::Convert(
162-
int significantDigits, const DataEdit &edit, int flags) {
163-
if (edit.modes.editingFlags & signPlus) {
164-
flags |= decimal::AlwaysSign;
165-
}
162+
int significantDigits, enum decimal::FortranRounding rounding, int flags) {
166163
auto converted{decimal::ConvertToDecimal<binaryPrecision>(buffer_,
167164
sizeof buffer_, static_cast<enum decimal::DecimalConversionFlags>(flags),
168-
significantDigits, edit.modes.round, x_)};
165+
significantDigits, rounding, x_)};
169166
if (!converted.str) { // overflow
170167
io_.GetIoErrorHandler().Crash(
171168
"RealOutputEditing::Convert : buffer size %zd was insufficient",
@@ -181,6 +178,9 @@ bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) {
181178
int editWidth{edit.width.value_or(0)}; // 'w' field
182179
int significantDigits{editDigits};
183180
int flags{0};
181+
if (edit.modes.editingFlags & signPlus) {
182+
flags |= decimal::AlwaysSign;
183+
}
184184
if (editWidth == 0) { // "the processor selects the field width"
185185
if (edit.digits.has_value()) { // E0.d
186186
editWidth = editDigits + 6; // -.666E+ee
@@ -204,7 +204,7 @@ bool RealOutputEditing<binaryPrecision>::EditEorDOutput(const DataEdit &edit) {
204204
// In EN editing, multiple attempts may be necessary, so it's in a loop.
205205
while (true) {
206206
decimal::ConversionToDecimalResult converted{
207-
Convert(significantDigits, edit, flags)};
207+
Convert(significantDigits, edit.modes.round, flags)};
208208
if (IsInfOrNaN(converted)) {
209209
return EmitPrefix(edit, converted.length, editWidth) &&
210210
io_.Emit(converted.str, converted.length) && EmitSuffix(edit);
@@ -261,7 +261,11 @@ template <int binaryPrecision>
261261
bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) {
262262
int fracDigits{edit.digits.value_or(0)}; // 'd' field
263263
const int editWidth{edit.width.value_or(0)}; // 'w' field
264+
enum decimal::FortranRounding rounding{edit.modes.round};
264265
int flags{0};
266+
if (edit.modes.editingFlags & signPlus) {
267+
flags |= decimal::AlwaysSign;
268+
}
265269
if (editWidth == 0) { // "the processor selects the field width"
266270
if (!edit.digits.has_value()) { // F0
267271
flags |= decimal::Minimize;
@@ -274,40 +278,59 @@ bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) {
274278
bool canIncrease{true};
275279
while (true) {
276280
decimal::ConversionToDecimalResult converted{
277-
Convert(extraDigits + fracDigits, edit, flags)};
281+
Convert(extraDigits + fracDigits, rounding, flags)};
278282
if (IsInfOrNaN(converted)) {
279283
return EmitPrefix(edit, converted.length, editWidth) &&
280284
io_.Emit(converted.str, converted.length) && EmitSuffix(edit);
281285
}
282286
int scale{IsZero() ? 1 : edit.modes.scale}; // kP
283287
int expo{converted.decimalExponent + scale};
288+
int signLength{*converted.str == '-' || *converted.str == '+' ? 1 : 0};
289+
int convertedDigits{static_cast<int>(converted.length) - signLength};
290+
int trailingOnes{0};
284291
if (expo > extraDigits && extraDigits >= 0 && canIncrease) {
285292
extraDigits = expo;
286293
if (!edit.digits.has_value()) { // F0
287294
fracDigits = sizeof buffer_ - extraDigits - 2; // sign & NUL
288295
}
289296
canIncrease = false; // only once
290297
continue;
298+
} else if (expo == -fracDigits && convertedDigits > 0) {
299+
if (rounding != decimal::FortranRounding::RoundToZero) {
300+
// Convert again without rounding so that we can round here
301+
rounding = decimal::FortranRounding::RoundToZero;
302+
continue;
303+
} else if (converted.str[signLength] >= '5') {
304+
// Value rounds up to a scaled 1 (e.g., 0.06 for F5.1 -> 0.1)
305+
++expo;
306+
convertedDigits = 0;
307+
trailingOnes = 1;
308+
} else {
309+
// Value rounds down to zero
310+
expo = 0;
311+
convertedDigits = 0;
312+
}
291313
} else if (expo < extraDigits && extraDigits > -fracDigits) {
292314
extraDigits = std::max(expo, -fracDigits);
293315
continue;
294316
}
295-
int signLength{*converted.str == '-' || *converted.str == '+' ? 1 : 0};
296-
int convertedDigits{static_cast<int>(converted.length) - signLength};
297317
int digitsBeforePoint{std::max(0, std::min(expo, convertedDigits))};
298318
int zeroesBeforePoint{std::max(0, expo - digitsBeforePoint)};
299319
int zeroesAfterPoint{std::min(fracDigits, std::max(0, -expo))};
300320
int digitsAfterPoint{convertedDigits - digitsBeforePoint};
301321
int trailingZeroes{flags & decimal::Minimize
302322
? 0
303-
: std::max(0, fracDigits - (zeroesAfterPoint + digitsAfterPoint))};
323+
: std::max(0,
324+
fracDigits -
325+
(zeroesAfterPoint + digitsAfterPoint + trailingOnes))};
304326
if (digitsBeforePoint + zeroesBeforePoint + zeroesAfterPoint +
305-
digitsAfterPoint + trailingZeroes ==
327+
digitsAfterPoint + trailingOnes + trailingZeroes ==
306328
0) {
307329
zeroesBeforePoint = 1; // "." -> "0."
308330
}
309331
int totalLength{signLength + digitsBeforePoint + zeroesBeforePoint +
310-
1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingZeroes};
332+
1 /*'.'*/ + zeroesAfterPoint + digitsAfterPoint + trailingOnes +
333+
trailingZeroes};
311334
int width{editWidth > 0 ? editWidth : totalLength};
312335
if (totalLength > width) {
313336
return io_.EmitRepeated('*', width);
@@ -323,6 +346,7 @@ bool RealOutputEditing<binaryPrecision>::EditFOutput(const DataEdit &edit) {
323346
io_.EmitRepeated('0', zeroesAfterPoint) &&
324347
io_.Emit(
325348
converted.str + signLength + digitsBeforePoint, digitsAfterPoint) &&
349+
io_.EmitRepeated('1', trailingOnes) &&
326350
io_.EmitRepeated('0', trailingZeroes) &&
327351
io_.EmitRepeated(' ', trailingBlanks_) && EmitSuffix(edit);
328352
}
@@ -337,8 +361,12 @@ DataEdit RealOutputEditing<binaryPrecision>::EditForGOutput(DataEdit edit) {
337361
if (!edit.width.has_value() || (*edit.width > 0 && significantDigits == 0)) {
338362
return edit; // Gw.0 -> Ew.0 for w > 0
339363
}
364+
int flags{0};
365+
if (edit.modes.editingFlags & signPlus) {
366+
flags |= decimal::AlwaysSign;
367+
}
340368
decimal::ConversionToDecimalResult converted{
341-
Convert(significantDigits, edit)};
369+
Convert(significantDigits, edit.modes.round, flags)};
342370
if (IsInfOrNaN(converted)) {
343371
return edit;
344372
}
@@ -365,7 +393,7 @@ DataEdit RealOutputEditing<binaryPrecision>::EditForGOutput(DataEdit edit) {
365393
template <int binaryPrecision>
366394
bool RealOutputEditing<binaryPrecision>::EditListDirectedOutput(
367395
const DataEdit &edit) {
368-
decimal::ConversionToDecimalResult converted{Convert(1, edit)};
396+
decimal::ConversionToDecimalResult converted{Convert(1, edit.modes.round)};
369397
if (IsInfOrNaN(converted)) {
370398
return EditEorDOutput(edit);
371399
}

flang/runtime/edit-output.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ template <int KIND> class RealOutputEditing : public RealOutputEditingBase {
8484
bool IsZero() const { return x_.IsZero(); }
8585

8686
decimal::ConversionToDecimalResult Convert(
87-
int significantDigits, const DataEdit &, int flags = 0);
87+
int significantDigits, enum decimal::FortranRounding, int flags = 0);
8888

8989
BinaryFloatingPoint x_;
9090
char buffer_[BinaryFloatingPoint::maxDecimalConversionDigits +

flang/unittests/Runtime/NumericalFormatTest.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,13 +633,17 @@ TEST(IOApiTests, FormatDoubleValues) {
633633
{"(F5.3,';')", 0.099999, "0.100;"},
634634
{"(F5.3,';')", 0.0099999, "0.010;"},
635635
{"(F5.3,';')", 0.00099999, "0.001;"},
636+
{"(F5.3,';')", 0.0005, "0.001;"},
637+
{"(F5.3,';')", 0.00049999, "0.000;"},
636638
{"(F5.3,';')", 0.000099999, "0.000;"},
637639
{"(F5.3,';')", -99.999, "*****;"},
638640
{"(F5.3,';')", -9.9999, "*****;"},
639641
{"(F5.3,';')", -0.99999, "*****;"},
640642
{"(F5.3,';')", -0.099999, "-.100;"},
641643
{"(F5.3,';')", -0.0099999, "-.010;"},
642644
{"(F5.3,';')", -0.00099999, "-.001;"},
645+
{"(F5.3,';')", -0.0005, "-.001;"},
646+
{"(F5.3,';')", -0.00049999, "-.000;"},
643647
{"(F5.3,';')", -0.000099999, "-.000;"},
644648
};
645649

0 commit comments

Comments
 (0)