diff --git a/flang/include/flang/Common/real.h b/flang/include/flang/Common/real.h index 036f665d3da61..50aab7d89a597 100644 --- a/flang/include/flang/Common/real.h +++ b/flang/include/flang/Common/real.h @@ -63,6 +63,10 @@ static constexpr int MaxDecimalConversionDigits(int binaryPrecision) { } } +static constexpr int MaxHexadecimalConversionDigits(int binaryPrecision) { + return binaryPrecision >= 0 ? (binaryPrecision + 3) / 4 : binaryPrecision; +} + static constexpr int RealKindForPrecision(int binaryPrecision) { switch (binaryPrecision) { case 8: // IEEE single (truncated): 1+8+7 with implicit bit @@ -132,6 +136,9 @@ template class RealDetails { static constexpr int maxDecimalConversionDigits{ MaxDecimalConversionDigits(binaryPrecision)}; + static constexpr int maxHexadecimalConversionDigits{ + MaxHexadecimalConversionDigits(binaryPrecision)}; + static_assert(binaryPrecision > 0); static_assert(exponentBits > 1); static_assert(exponentBits <= 15); diff --git a/flang/include/flang/Decimal/binary-floating-point.h b/flang/include/flang/Decimal/binary-floating-point.h index 28346e71828fd..b9346a8585e2d 100644 --- a/flang/include/flang/Decimal/binary-floating-point.h +++ b/flang/include/flang/Decimal/binary-floating-point.h @@ -21,10 +21,19 @@ namespace Fortran::decimal { +enum FortranRounding { + RoundNearest, /* RN and RP */ + RoundUp, /* RU */ + RoundDown, /* RD */ + RoundToZero, /* RZ - no rounding */ + RoundCompatible, /* RC: like RN, but ties go away from 0 */ +}; + template class BinaryFloatingPointNumber : public common::RealDetails { public: using Details = common::RealDetails; + using Details::binaryPrecision; using Details::bits; using Details::decimalPrecision; using Details::decimalRange; @@ -33,6 +42,7 @@ class BinaryFloatingPointNumber : public common::RealDetails { using Details::isImplicitMSB; using Details::maxDecimalConversionDigits; using Details::maxExponent; + using Details::maxHexadecimalConversionDigits; using Details::significandBits; using RawType = common::HostUnsignedIntType; @@ -120,6 +130,55 @@ class BinaryFloatingPointNumber : public common::RealDetails { InsertExplicitMSB(); } + static constexpr BinaryFloatingPointNumber Infinity(bool isNegative) { + RawType result{RawType{maxExponent} << significandBits}; + if (isNegative) { + result |= RawType{1} << (bits - 1); + } + return BinaryFloatingPointNumber{result}; + } + + // Returns true when the result is exact + constexpr bool RoundToBits(int keepBits, enum FortranRounding mode) { + if (IsNaN() || IsInfinite() || keepBits >= binaryPrecision) { + return true; + } + int lostBits{binaryPrecision - keepBits}; + RawType lostMask{static_cast((RawType{1} << lostBits) - 1)}; + if (RawType lost{static_cast(raw_ & lostMask)}; lost != 0) { + bool increase{false}; + switch (mode) { + case RoundNearest: + if (lost >> (lostBits - 1) != 0) { // >= tie + if ((lost & (lostMask >> 1)) != 0) { + increase = true; // > tie + } else { + increase = ((raw_ >> lostBits) & 1) != 0; // tie to even + } + } + break; + case RoundUp: + increase = !IsNegative(); + break; + case RoundDown: + increase = IsNegative(); + break; + case RoundToZero: + break; + case RoundCompatible: + increase = lost >> (lostBits - 1) != 0; // >= tie + break; + } + if (increase) { + raw_ |= lostMask; + Next(); + } + return false; // inexact + } else { + return true; // exact + } + } + private: constexpr void RemoveExplicitMSB() { if constexpr (!isImplicitMSB) { diff --git a/flang/include/flang/Decimal/decimal.h b/flang/include/flang/Decimal/decimal.h index b9ac6b71cd03a..a4e0ee7c84746 100644 --- a/flang/include/flang/Decimal/decimal.h +++ b/flang/include/flang/Decimal/decimal.h @@ -43,14 +43,6 @@ struct ConversionToDecimalResult { enum ConversionResultFlags flags; }; -enum FortranRounding { - RoundNearest, /* RN and RP */ - RoundUp, /* RU */ - RoundDown, /* RD */ - RoundToZero, /* RZ - no rounding */ - RoundCompatible, /* RC: like RN, but ties go away from 0 */ -}; - /* The "minimize" flag causes the fewest number of output digits * to be emitted such that reading them back into the same binary * floating-point format with RoundNearest will return the same diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp index 1861c9f8499b0..4e8c9aa868a69 100644 --- a/flang/runtime/edit-input.cpp +++ b/flang/runtime/edit-input.cpp @@ -125,7 +125,7 @@ static bool EditBOZInput( return CheckCompleteListDirectedField(io, edit); } -static inline char32_t GetDecimalPoint(const DataEdit &edit) { +static inline char32_t GetRadixPointChar(const DataEdit &edit) { return edit.modes.editingFlags & decimalComma ? char32_t{','} : char32_t{'.'}; } @@ -229,17 +229,22 @@ bool EditIntegerInput( // Parses a REAL input number from the input source as a normalized // fraction into a supplied buffer -- there's an optional '-', a -// decimal point, and at least one digit. The adjusted exponent value -// is returned in a reference argument. The returned value is the number -// of characters that (should) have been written to the buffer -- this can -// be larger than the buffer size and can indicate overflow. Replaces -// blanks with zeroes if appropriate. -static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, - const DataEdit &edit, int &exponent) { +// decimal point when the input is not hexadecimal, and at least one +// digit. Replaces blanks with zeroes where appropriate. +struct ScannedRealInput { + // Number of characters that (should) have been written to the + // buffer -- this can be larger than the buffer size, which + // indicates buffer overflow. Zero indicates an error. + int got{0}; + int exponent{0}; // adjusted as necessary; binary if isHexadecimal + bool isHexadecimal{false}; // 0X... +}; +static ScannedRealInput ScanRealInput( + char *buffer, int bufferSize, IoStatementState &io, const DataEdit &edit) { std::optional remaining; std::optional next; int got{0}; - std::optional decimalPoint; + std::optional radixPointOffset; auto Put{[&](char ch) -> void { if (got < bufferSize) { buffer[got] = ch; @@ -251,6 +256,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, Put('-'); } bool bzMode{(edit.modes.editingFlags & blankZero) != 0}; + int exponent{0}; if (!next || (!bzMode && *next == ' ')) { if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) { // An empty/blank field means zero when not list-directed. @@ -259,10 +265,11 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, // required to pass FCVS. Put('0'); } - return got; + return {got, exponent, false}; } - char32_t decimal{GetDecimalPoint(edit)}; + char32_t radixPointChar{GetRadixPointChar(edit)}; char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next}; + bool isHexadecimal{false}; if (first == 'N' || first == 'I') { // NaN or infinity - convert to upper case // Subtle: a blank field of digits could be followed by 'E' or 'D', @@ -283,7 +290,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, if (depth == 0) { break; } else if (!next) { - return 0; // error + return {}; // error } else if (*next == '(') { ++depth; } else if (*next == ')') { @@ -292,34 +299,51 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, Put(*next); } } - exponent = 0; - } else if (first == decimal || (first >= '0' && first <= '9') || + } else if (first == radixPointChar || (first >= '0' && first <= '9') || (bzMode && (first == ' ' || first == '\t')) || first == 'E' || first == 'D' || first == 'Q') { - Put('.'); // input field is normalized to a fraction + if (first == '0') { + next = io.NextInField(remaining, edit); + if (next && (*next == 'x' || *next == 'X')) { // 0X... + isHexadecimal = true; + next = io.NextInField(remaining, edit); + } else { + Put('0'); + } + } + // input field is normalized to a fraction + if (!isHexadecimal) { + Put('.'); + } auto start{got}; for (; next; next = io.NextInField(remaining, edit)) { char32_t ch{*next}; if (ch == ' ' || ch == '\t') { - if (bzMode) { + if (isHexadecimal) { + return {}; // error + } else if (bzMode) { ch = '0'; // BZ mode - treat blank as if it were zero } else { - continue; + continue; // ignore blank in fixed field } } - if (ch == '0' && got == start && !decimalPoint) { - // omit leading zeroes before the decimal + if (ch == '0' && got == start && !radixPointOffset) { + // omit leading zeroes before the radix point } else if (ch >= '0' && ch <= '9') { Put(ch); - } else if (ch == decimal && !decimalPoint) { - // the decimal point is *not* copied to the buffer - decimalPoint = got - start; // # of digits before the decimal point + } else if (ch == radixPointChar && !radixPointOffset) { + // The radix point character is *not* copied to the buffer. + radixPointOffset = got - start; // # of digits before the radix point + } else if (isHexadecimal && ch >= 'A' && ch <= 'F') { + Put(ch); + } else if (isHexadecimal && ch >= 'a' && ch <= 'f') { + Put(ch - 'a' + 'A'); // normalize to capitals } else { break; } } if (got == start) { - // Nothing but zeroes and maybe a decimal point. F'2018 requires + // Nothing but zeroes and maybe a radix point. F'2018 requires // at least one digit, but F'77 did not, and a bare "." shows up in // the FCVS suite. Put('0'); // emit at least one digit @@ -328,17 +352,22 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, auto nextBeforeExponent{next}; auto startExponent{io.GetConnectionState().positionInRecord}; bool hasGoodExponent{false}; - if (next && - (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' || - *next == 'q' || *next == 'Q')) { - // Optional exponent letter. Blanks are allowed between the - // optional exponent letter and the exponent value. - io.SkipSpaces(remaining); - next = io.NextInField(remaining, edit); + if (next) { + if (isHexadecimal) { + if (*next == 'p' || *next == 'P') { + next = io.NextInField(remaining, edit); + } else { + // The binary exponent is not optional in the standard. + return {}; // error + } + } else if (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' || + *next == 'q' || *next == 'Q') { + // Optional exponent letter. Blanks are allowed between the + // optional exponent letter and the exponent value. + io.SkipSpaces(remaining); + next = io.NextInField(remaining, edit); + } } - // The default exponent is -kP, but the scale factor doesn't affect - // an explicit exponent. - exponent = -edit.modes.scale; if (next && (*next == '-' || *next == '+' || (*next >= '0' && *next <= '9') || *next == ' ' || *next == '\t')) { @@ -346,14 +375,16 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, if (negExpo || *next == '+') { next = io.NextInField(remaining, edit); } - for (exponent = 0; next; next = io.NextInField(remaining, edit)) { + for (; next; next = io.NextInField(remaining, edit)) { if (*next >= '0' && *next <= '9') { hasGoodExponent = true; if (exponent < 10000) { exponent = 10 * exponent + *next - '0'; } } else if (*next == ' ' || *next == '\t') { - if (bzMode) { + if (isHexadecimal) { + break; + } else if (bzMode) { hasGoodExponent = true; exponent = 10 * exponent; } @@ -366,23 +397,29 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, } } if (!hasGoodExponent) { + if (isHexadecimal) { + return {}; // error + } // There isn't a good exponent; do not consume it. next = nextBeforeExponent; io.HandleAbsolutePosition(startExponent); - } - if (decimalPoint) { - exponent += *decimalPoint; + // The default exponent is -kP, but the scale factor doesn't affect + // an explicit exponent. + exponent = -edit.modes.scale; + } + // Adjust exponent by number of digits before the radix point. + if (isHexadecimal) { + // Exponents for hexadecimal input are binary. + exponent += radixPointOffset.value_or(got - start) * 4; + } else if (radixPointOffset) { + exponent += *radixPointOffset; } else { - // When no decimal point (or comma) appears in the value, the 'd' + // When no redix point (or comma) appears in the value, the 'd' // part of the edit descriptor must be interpreted as the number of // digits in the value to be interpreted as being to the *right* of - // the assumed decimal point (13.7.2.3.2) + // the assumed radix point (13.7.2.3.2) exponent += got - start - edit.digits.value_or(0); } - } else { - // TODO: hex FP input - exponent = 0; - return 0; } // Consume the trailing ')' of a list-directed or NAMELIST complex // input value. @@ -403,10 +440,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io, next = io.NextInField(remaining, edit); } if (next) { - return 0; // error: unused nonblank character in fixed-width field + return {}; // error: unused nonblank character in fixed-width field } } - return got; + return {got, exponent, isHexadecimal}; } static void RaiseFPExceptions(decimal::ConversionResultFlags flags) { @@ -433,7 +470,7 @@ static void RaiseFPExceptions(decimal::ConversionResultFlags flags) { // converter without modification, this fast path for real input // saves time by avoiding memory copies and reformatting of the exponent. template -static bool TryFastPathRealInput( +static bool TryFastPathRealDecimalInput( IoStatementState &io, const DataEdit &edit, void *n) { if (edit.modes.editingFlags & (blankZero | decimalComma)) { return false; @@ -504,10 +541,103 @@ static bool TryFastPathRealInput( return true; } +template +decimal::ConversionToBinaryResult ConvertHexadecimal( + const char *&p, enum decimal::FortranRounding rounding, int expo) { + using RealType = decimal::BinaryFloatingPointNumber; + using RawType = typename RealType::RawType; + bool isNegative{*p == '-'}; + constexpr RawType one{1}; + RawType signBit{0}; + if (isNegative) { + ++p; + signBit = one << (RealType::bits - 1); + } + RawType fraction{0}; + // Adjust the incoming binary P+/- exponent to shift the radix point + // to below the LSB and add in the bias. + expo += binaryPrecision - 1 + RealType::exponentBias; + // Input the fraction. + int roundingBit{0}; + int guardBit{0}; + for (; *p; ++p) { + fraction <<= 4; + expo -= 4; + if (*p >= '0' && *p <= '9') { + fraction |= *p - '0'; + } else if (*p >= 'A' && *p <= 'F') { + fraction |= *p - 'A' + 10; // data were normalized to capitals + } else { + break; + } + while (fraction >> binaryPrecision) { + guardBit |= roundingBit; + roundingBit = (int)fraction & 1; + fraction >>= 1; + ++expo; + } + } + if (fraction) { + // Boost biased expo if too small + while (expo < 1) { + guardBit |= roundingBit; + roundingBit = (int)fraction & 1; + fraction >>= 1; + ++expo; + } + // Normalize + while (expo > 1 && !(fraction >> (binaryPrecision - 1))) { + fraction <<= 1; + --expo; + } + // Rounding + bool increase{false}; + switch (rounding) { + case decimal::RoundNearest: // RN & RP + increase = roundingBit && (guardBit | ((int)fraction & 1)); + break; + case decimal::RoundUp: // RU + increase = !isNegative && (roundingBit | guardBit); + break; + case decimal::RoundDown: // RD + increase = isNegative && (roundingBit | guardBit); + break; + case decimal::RoundToZero: // RZ + break; + case decimal::RoundCompatible: // RC + increase = roundingBit != 0; + break; + } + if (increase) { + ++fraction; + if (fraction >> binaryPrecision) { + fraction >>= 1; + ++expo; + } + } + } + // Package & return result + constexpr RawType significandMask{(one << RealType::significandBits) - 1}; + if (!fraction) { + expo = 0; + } else if (expo == 1 && !(fraction >> (binaryPrecision - 1))) { + expo = 0; // subnormal + } else if (expo >= RealType::maxExponent) { + expo = RealType::maxExponent; // +/-Inf + fraction = 0; + } else { + fraction &= significandMask; // remove explicit normalization unless x87 + } + return decimal::ConversionToBinaryResult{ + RealType{static_cast(signBit | + static_cast(expo) << RealType::significandBits | fraction)}, + (roundingBit | guardBit) ? decimal::Inexact : decimal::Exact}; +} + template bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) { constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)}; - if (TryFastPathRealInput(io, edit, n)) { + if (TryFastPathRealDecimalInput(io, edit, n)) { return CheckCompleteListDirectedField(io, edit); } // Fast path wasn't available or didn't work; go the more general route @@ -515,8 +645,8 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) { common::MaxDecimalConversionDigits(binaryPrecision)}; static constexpr int bufferSize{maxDigits + 18}; char buffer[bufferSize]; - int exponent{0}; - int got{ScanRealInput(buffer, maxDigits + 2, io, edit, exponent)}; + auto scanned{ScanRealInput(buffer, maxDigits + 2, io, edit)}; + int got{scanned.got}; if (got >= maxDigits + 2) { io.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small"); return false; @@ -529,48 +659,55 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) { static_cast(connection.currentRecordNumber)); return false; } - bool hadExtra{got > maxDigits}; - if (exponent != 0) { - buffer[got++] = 'e'; - if (exponent < 0) { - buffer[got++] = '-'; - exponent = -exponent; - } - if (exponent > 9999) { - exponent = 9999; // will convert to +/-Inf - } - if (exponent > 999) { - int dig{exponent / 1000}; - buffer[got++] = '0' + dig; - int rest{exponent - 1000 * dig}; - dig = rest / 100; - buffer[got++] = '0' + dig; - rest -= 100 * dig; - dig = rest / 10; - buffer[got++] = '0' + dig; - buffer[got++] = '0' + (rest - 10 * dig); - } else if (exponent > 99) { - int dig{exponent / 100}; - buffer[got++] = '0' + dig; - int rest{exponent - 100 * dig}; - dig = rest / 10; - buffer[got++] = '0' + dig; - buffer[got++] = '0' + (rest - 10 * dig); - } else if (exponent > 9) { - int dig{exponent / 10}; - buffer[got++] = '0' + dig; - buffer[got++] = '0' + (exponent - 10 * dig); - } else { - buffer[got++] = '0' + exponent; - } - } - buffer[got] = '\0'; + decimal::ConversionToBinaryResult converted; const char *p{buffer}; - decimal::ConversionToBinaryResult converted{ - decimal::ConvertToBinary(p, edit.modes.round)}; - if (hadExtra) { - converted.flags = static_cast( - converted.flags | decimal::Inexact); + if (scanned.isHexadecimal) { + buffer[got] = '\0'; + converted = ConvertHexadecimal( + p, edit.modes.round, scanned.exponent); + } else { + bool hadExtra{got > maxDigits}; + int exponent{scanned.exponent}; + if (exponent != 0) { + buffer[got++] = 'e'; + if (exponent < 0) { + buffer[got++] = '-'; + exponent = -exponent; + } + if (exponent > 9999) { + exponent = 9999; // will convert to +/-Inf + } + if (exponent > 999) { + int dig{exponent / 1000}; + buffer[got++] = '0' + dig; + int rest{exponent - 1000 * dig}; + dig = rest / 100; + buffer[got++] = '0' + dig; + rest -= 100 * dig; + dig = rest / 10; + buffer[got++] = '0' + dig; + buffer[got++] = '0' + (rest - 10 * dig); + } else if (exponent > 99) { + int dig{exponent / 100}; + buffer[got++] = '0' + dig; + int rest{exponent - 100 * dig}; + dig = rest / 10; + buffer[got++] = '0' + dig; + buffer[got++] = '0' + (rest - 10 * dig); + } else if (exponent > 9) { + int dig{exponent / 10}; + buffer[got++] = '0' + dig; + buffer[got++] = '0' + (exponent - 10 * dig); + } else { + buffer[got++] = '0' + exponent; + } + } + buffer[got] = '\0'; + converted = decimal::ConvertToBinary(p, edit.modes.round); + if (hadExtra) { + converted.flags = static_cast( + converted.flags | decimal::Inexact); + } } if (*p) { // unprocessed junk after value const auto &connection{io.GetConnectionState()}; diff --git a/flang/runtime/edit-output.cpp b/flang/runtime/edit-output.cpp index be0bb07f08bfe..18b209bc6798c 100644 --- a/flang/runtime/edit-output.cpp +++ b/flang/runtime/edit-output.cpp @@ -205,13 +205,20 @@ const char *RealOutputEditingBase::FormatExponent( } else if (exponent == eEnd) { *--exponent = '0'; // Ew.dE0 with zero-valued exponent } - } else { // ensure at least two exponent digits + } else if (edit.variation == 'X') { + if (expo == 0) { + *--exponent = '0'; // EX without Ee and zero-valued exponent + } + } else { + // Ensure at least two exponent digits unless EX while (exponent + 2 > eEnd) { *--exponent = '0'; } } *--exponent = expo < 0 ? '-' : '+'; - if (edit.expoDigits || edit.IsListDirected() || exponent + 3 == eEnd) { + if (edit.variation == 'X') { + *--exponent = 'P'; + } else if (edit.expoDigits || edit.IsListDirected() || exponent + 3 == eEnd) { *--exponent = edit.descriptor == 'D' ? 'D' : 'E'; // not 'G' or 'Q' } length = eEnd - exponent; @@ -251,19 +258,32 @@ bool RealOutputEditingBase::EmitSuffix(const DataEdit &edit) { } template -decimal::ConversionToDecimalResult RealOutputEditing::Convert( +decimal::ConversionToDecimalResult RealOutputEditing::ConvertToDecimal( int significantDigits, enum decimal::FortranRounding rounding, int flags) { auto converted{decimal::ConvertToDecimal(buffer_, sizeof buffer_, static_cast(flags), significantDigits, rounding, x_)}; if (!converted.str) { // overflow io_.GetIoErrorHandler().Crash( - "RealOutputEditing::Convert : buffer size %zd was insufficient", + "RealOutputEditing::ConvertToDecimal: buffer size %zd was insufficient", sizeof buffer_); } return converted; } +static bool IsInfOrNaN(const char *p, int length) { + if (!p || length < 1) { + return false; + } + if (*p == '-' || *p == '+') { + if (length == 1) { + return false; + } + ++p; + } + return *p == 'I' || *p == 'N'; +} + // 13.7.2.3.3 in F'2018 template bool RealOutputEditing::EditEorDOutput(const DataEdit &edit) { @@ -275,7 +295,6 @@ bool RealOutputEditing::EditEorDOutput(const DataEdit &edit) { if (edit.modes.editingFlags & signPlus) { flags |= decimal::AlwaysSign; } - bool noLeadingSpaces{editWidth == 0}; int scale{edit.modes.scale}; // 'kP' value if (editWidth == 0) { // "the processor selects the field width" if (edit.digits.has_value()) { // E0.d @@ -319,8 +338,8 @@ bool RealOutputEditing::EditEorDOutput(const DataEdit &edit) { // In EN editing, multiple attempts may be necessary, so this is a loop. while (true) { decimal::ConversionToDecimalResult converted{ - Convert(significantDigits, edit.modes.round, flags)}; - if (IsInfOrNaN(converted)) { + ConvertToDecimal(significantDigits, edit.modes.round, flags)}; + if (IsInfOrNaN(converted.str, static_cast(converted.length))) { return editWidth > 0 && converted.length > static_cast(editWidth) ? EmitRepeated(io_, '*', editWidth) @@ -380,7 +399,7 @@ bool RealOutputEditing::EditEorDOutput(const DataEdit &edit) { zeroesBeforePoint = 1; ++totalLength; } - if (totalLength < width && noLeadingSpaces) { + if (totalLength < width && editWidth == 0) { width = totalLength; } return EmitPrefix(edit, totalLength, width) && @@ -418,8 +437,8 @@ bool RealOutputEditing::EditFOutput(const DataEdit &edit) { bool canIncrease{true}; while (true) { decimal::ConversionToDecimalResult converted{ - Convert(extraDigits + fracDigits, rounding, flags)}; - if (IsInfOrNaN(converted)) { + ConvertToDecimal(extraDigits + fracDigits, rounding, flags)}; + if (IsInfOrNaN(converted.str, static_cast(converted.length))) { return editWidth > 0 && converted.length > static_cast(editWidth) ? EmitRepeated(io_, '*', editWidth) @@ -521,8 +540,8 @@ DataEdit RealOutputEditing::EditForGOutput(DataEdit edit) { flags |= decimal::AlwaysSign; } decimal::ConversionToDecimalResult converted{ - Convert(significantDigits, edit.modes.round, flags)}; - if (IsInfOrNaN(converted)) { + ConvertToDecimal(significantDigits, edit.modes.round, flags)}; + if (IsInfOrNaN(converted.str, static_cast(converted.length))) { return edit; // Inf/Nan -> Ew.d (same as Fw.d) } int expo{IsZero() ? 1 : converted.decimalExponent}; // 's' @@ -549,8 +568,9 @@ DataEdit RealOutputEditing::EditForGOutput(DataEdit edit) { // 13.10.4 in F'2018 template bool RealOutputEditing::EditListDirectedOutput(const DataEdit &edit) { - decimal::ConversionToDecimalResult converted{Convert(1, edit.modes.round)}; - if (IsInfOrNaN(converted)) { + decimal::ConversionToDecimalResult converted{ + ConvertToDecimal(1, edit.modes.round)}; + if (IsInfOrNaN(converted.str, static_cast(converted.length))) { return EditEorDOutput(edit); } int expo{converted.decimalExponent}; @@ -567,11 +587,120 @@ bool RealOutputEditing::EditListDirectedOutput(const DataEdit &edit) { return EditFOutput(edit); } -// 13.7.5.2.6 in F'2018 +// 13.7.2.3.6 in F'2023 +// The specification for hexadecimal output, unfortunately for implementors, +// leaves as "implementation dependent" the choice of how to emit values +// with multiple hexadecimal output possibilities that are numerically +// equivalent. The one working implementation of EX output that I can find +// apparently chooses to frame the nybbles from most to least significant, +// rather than trying to minimize the magnitude of the binary exponent. +// E.g., 2. is edited into 0X8.0P-2 rather than 0X2.0P0. This implementation +// follows that precedent so as to avoid a gratuitous incompatibility. template -bool RealOutputEditing::EditEXOutput(const DataEdit &) { - io_.GetIoErrorHandler().Crash( - "not yet implemented: EX output editing"); // TODO +auto RealOutputEditing::ConvertToHexadecimal( + int significantDigits, enum decimal::FortranRounding rounding, int flags) + -> ConvertToHexadecimalResult { + if (x_.IsNaN() || x_.IsInfinite()) { + auto converted{ConvertToDecimal(significantDigits, rounding, flags)}; + return {converted.str, static_cast(converted.length), 0}; + } + x_.RoundToBits(4 * significantDigits, rounding); + if (x_.IsInfinite()) { // rounded away to +/-Inf + auto converted{ConvertToDecimal(significantDigits, rounding, flags)}; + return {converted.str, static_cast(converted.length), 0}; + } + int len{0}; + if (x_.IsNegative()) { + buffer_[len++] = '-'; + } else if (flags & decimal::AlwaysSign) { + buffer_[len++] = '+'; + } + auto fraction{x_.Fraction()}; + if (fraction == 0) { + buffer_[len++] = '0'; + return {buffer_, len, 0}; + } else { + // Ensure that the MSB is set. + int expo{x_.UnbiasedExponent() - 3}; + while (!(fraction >> (x_.binaryPrecision - 1))) { + fraction <<= 1; + --expo; + } + // This is initially the right shift count needed to bring the + // most-significant hexadecimal digit's bits into the LSBs. + // x_.binaryPrecision is constant, so / can be used for readability. + int shift{x_.binaryPrecision - 4}; + typename BinaryFloatingPoint::RawType one{1}; + auto remaining{(one << shift) - one}; + for (int digits{0}; digits < significantDigits; ++digits) { + if ((flags & decimal::Minimize) && !(fraction & remaining)) { + break; + } + int hexDigit{0}; + if (shift >= 0) { + hexDigit = int(fraction >> shift) & 0xf; + } else if (shift >= -3) { + hexDigit = int(fraction << -shift) & 0xf; + } + if (hexDigit >= 10) { + buffer_[len++] = 'A' + hexDigit - 10; + } else { + buffer_[len++] = '0' + hexDigit; + } + shift -= 4; + remaining >>= 4; + } + return {buffer_, len, expo}; + } +} + +template +bool RealOutputEditing::EditEXOutput(const DataEdit &edit) { + addSpaceBeforeCharacter(io_); + int editDigits{edit.digits.value_or(0)}; // 'd' field + int significantDigits{editDigits + 1}; + int flags{0}; + if (edit.modes.editingFlags & signPlus) { + flags |= decimal::AlwaysSign; + } + int editWidth{edit.width.value_or(0)}; // 'w' field + if (editWidth == 0 && !edit.digits) { // EX0 (no .d) + flags |= decimal::Minimize; + significantDigits = 28; // enough for 128-bit F.P. + } + auto converted{ + ConvertToHexadecimal(significantDigits, edit.modes.round, flags)}; + if (IsInfOrNaN(converted.str, converted.length)) { + return editWidth > 0 && converted.length > editWidth + ? EmitRepeated(io_, '*', editWidth) + : (editWidth <= converted.length || + EmitRepeated(io_, ' ', editWidth - converted.length)) && + EmitAscii(io_, converted.str, converted.length); + } + int signLength{converted.length > 0 && + (converted.str[0] == '-' || converted.str[0] == '+') + ? 1 + : 0}; + int convertedDigits{converted.length - signLength}; + int expoLength{0}; + const char *exponent{FormatExponent(converted.exponent, edit, expoLength)}; + int trailingZeroes{flags & decimal::Minimize + ? 0 + : std::max(0, significantDigits - convertedDigits)}; + int totalLength{converted.length + trailingZeroes + expoLength + 3 /*0X.*/}; + int width{editWidth > 0 ? editWidth : totalLength}; + return totalLength > width || !exponent + ? EmitRepeated(io_, '*', width) + : EmitRepeated(io_, ' ', width - totalLength) && + EmitAscii(io_, converted.str, signLength) && + EmitAscii(io_, "0X", 2) && + EmitAscii(io_, converted.str + signLength, 1) && + EmitAscii( + io_, edit.modes.editingFlags & decimalComma ? "," : ".", 1) && + EmitAscii(io_, converted.str + signLength + 1, + converted.length - (signLength + 1)) && + EmitRepeated(io_, '0', trailingZeroes) && + EmitAscii(io_, exponent, expoLength); } template bool RealOutputEditing::Edit(const DataEdit &edit) { diff --git a/flang/runtime/edit-output.h b/flang/runtime/edit-output.h index 765e41f89827d..4e6d6b25b4dd2 100644 --- a/flang/runtime/edit-output.h +++ b/flang/runtime/edit-output.h @@ -38,20 +38,6 @@ class RealOutputEditingBase { protected: explicit RealOutputEditingBase(IoStatementState &io) : io_{io} {} - static bool IsInfOrNaN(const decimal::ConversionToDecimalResult &res) { - const char *p{res.str}; - if (!p || res.length < 1) { - return false; - } - if (*p == '-' || *p == '+') { - if (res.length == 1) { - return false; - } - ++p; - } - return *p < '0' || *p > '9'; - } - // Returns null when the exponent overflows a fixed-size output field. const char *FormatExponent(int, const DataEdit &edit, int &length); bool EmitPrefix(const DataEdit &, std::size_t length, std::size_t width); @@ -84,7 +70,15 @@ template class RealOutputEditing : public RealOutputEditingBase { bool IsZero() const { return x_.IsZero(); } - decimal::ConversionToDecimalResult Convert( + decimal::ConversionToDecimalResult ConvertToDecimal( + int significantDigits, enum decimal::FortranRounding, int flags = 0); + + struct ConvertToHexadecimalResult { + const char *str; + int length; + int exponent; + }; + ConvertToHexadecimalResult ConvertToHexadecimal( int significantDigits, enum decimal::FortranRounding, int flags = 0); BinaryFloatingPoint x_; diff --git a/flang/unittests/Runtime/NumericalFormatTest.cpp b/flang/unittests/Runtime/NumericalFormatTest.cpp index 833b16be0fc3f..219947fe4fbbb 100644 --- a/flang/unittests/Runtime/NumericalFormatTest.cpp +++ b/flang/unittests/Runtime/NumericalFormatTest.cpp @@ -290,6 +290,8 @@ TEST(IOApiTests, FormatZeroes) { {"(1P,G32.17,';')", " 0.0000000000000000 ;"}, {"(2P,E32.17,';')", " 00.0000000000000000E+00;"}, {"(-1P,E32.17,';')", " 0.00000000000000000E+00;"}, + {"(EX32.17,';')", " 0X0.00000000000000000P+0;"}, + {"(DC,EX32.17,';')", " 0X0,00000000000000000P+0;"}, {"(G0,';')", "0.;"}, }; @@ -321,6 +323,8 @@ TEST(IOApiTests, FormatOnes) { {"(2P,G32.17,';')", " 1.0000000000000000 ;"}, {"(-1P,E32.17,';')", " 0.01000000000000000E+02;"}, {"(-1P,G32.17,';')", " 1.0000000000000000 ;"}, + {"(EX32.17,';')", " 0X8.00000000000000000P-3;"}, + {"(DC,EX32.17,';')", " 0X8,00000000000000000P-3;"}, {"(G0,';')", "1.;"}, }; @@ -337,6 +341,7 @@ TEST(IOApiTests, FormatNegativeOnes) { {"(E32.17,';')", " -0.10000000000000000E+01;"}, {"(F32.17,';')", " -1.00000000000000000;"}, {"(G32.17,';')", " -1.0000000000000000 ;"}, + {"(EX32.17,';')", " -0X8.00000000000000000P-3;"}, {"(G0,';')", "-1.;"}, }; for (auto const &[format, expect] : negOnes) { @@ -365,6 +370,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(G8.1,';')", " -0. ;"}, {"(G0,';')", "-0.;"}, {"(E9.1,';')", " -0.0E+00;"}, + {"(EX9.1,';')", "-0X0.0P+0;"}, }}, {// +Inf 0x7ff0000000000000, @@ -372,9 +378,11 @@ TEST(IOApiTests, FormatDoubleValues) { {"(E9.1,';')", " Inf;"}, {"(F9.1,';')", " Inf;"}, {"(G9.1,';')", " Inf;"}, + {"(EX9.1,';')", " Inf;"}, {"(SP,E9.1,';')", " +Inf;"}, {"(SP,F9.1,';')", " +Inf;"}, {"(SP,G9.1,';')", " +Inf;"}, + {"(SP,EX9.1,';')", " +Inf;"}, {"(G0,';')", "Inf;"}, }}, {// -Inf @@ -383,6 +391,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(E9.1,';')", " -Inf;"}, {"(F9.1,';')", " -Inf;"}, {"(G9.1,';')", " -Inf;"}, + {"(EX9.1,';')", " -Inf;"}, {"(G0,';')", "-Inf;"}, }}, {// NaN @@ -391,6 +400,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(E9.1,';')", " NaN;"}, {"(F9.1,';')", " NaN;"}, {"(G9.1,';')", " NaN;"}, + {"(EX9.1,';')", " NaN;"}, {"(G0,';')", "NaN;"}, }}, {// NaN (sign irrelevant) @@ -402,6 +412,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(SP,E9.1,';')", " NaN;"}, {"(SP,F9.1,';')", " NaN;"}, {"(SP,G9.1,';')", " NaN;"}, + {"(SP,EX9.1,';')", " NaN;"}, {"(G0,';')", "NaN;"}, }}, {// 0.1 rounded @@ -429,6 +440,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(G0.55,';')", ".1000000000000000055511151231257827021181583404541015625;"}, {"(G0,';')", ".1;"}, + {"(EX20.12,';')", " 0XC.CCCCCCCCCCCDP-7;"}, }}, {// 1.5 0x3ff8000000000000, @@ -436,6 +448,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(E9.2,';')", " 0.15E+01;"}, {"(F4.1,';')", " 1.5;"}, {"(G7.1,';')", " 2. ;"}, + {"(EX9.1,';')", " 0XC.0P-3;"}, {"(RN,E8.1,';')", " 0.2E+01;"}, {"(RN,F3.0,';')", " 2.;"}, {"(RN,G7.0,';')", " 0.E+01;"}, @@ -465,6 +478,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(RU,E8.1,';')", "-0.1E+01;"}, {"(RZ,E8.1,';')", "-0.1E+01;"}, {"(RC,E8.1,';')", "-0.2E+01;"}, + {"(EX9.1,';')", "-0XC.0P-3;"}, }}, {// 2.5 0x4004000000000000, @@ -475,6 +489,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(RU,E8.1,';')", " 0.3E+01;"}, {"(RZ,E8.1,';')", " 0.2E+01;"}, {"(RC,E8.1,';')", " 0.3E+01;"}, + {"(EX9.1,';')", " 0XA.0P-2;"}, }}, {// -2.5 0xc004000000000000, @@ -485,6 +500,7 @@ TEST(IOApiTests, FormatDoubleValues) { {"(RU,E8.1,';')", "-0.2E+01;"}, {"(RZ,E8.1,';')", "-0.2E+01;"}, {"(RC,E8.1,';')", "-0.3E+01;"}, + {"(EX9.1,';')", "-0XA.0P-2;"}, }}, {// least positive nonzero subnormal 1, @@ -583,6 +599,7 @@ TEST(IOApiTests, FormatDoubleValues) { "701797267771758512566055119913150489110145103786273816725095" "583738973359899366480994116420570263709027924276754456522908" "753868250641971826553344726563-323;"}, + {"(EX24.13,';')", " 0X8.0000000000000P-1077;"}, }}, {// least positive nonzero normal 0x10000000000000, @@ -603,6 +620,7 @@ TEST(IOApiTests, FormatDoubleValues) { "61364675687023986783152906809846172109246253967285156250-" "307;"}, {"(G0,';')", ".22250738585072014E-307;"}, + {"(EX24.13,';')", " 0X8.0000000000000P-1025;"}, }}, {// greatest finite 0x7fefffffffffffffuLL, @@ -633,6 +651,31 @@ TEST(IOApiTests, FormatDoubleValues) { "123348274797826204144723168738177180919299881250404026184124" "8583680000+306;"}, {"(G0,';')", ".17976931348623157E+309;"}, + {"(EX24.13,';')", " 0XF.FFFFFFFFFFFF8P+1020;"}, + }}, + {// EX rounding + 0x3ff1000000000000uLL, // 1.0625 + { + {"(F7.4,';')", " 1.0625;"}, + {"(EX9.1,';')", " 0X8.8P-3;"}, + {"(EX9.0,';')", " 0X8.P-3;"}, + {"(RN,EX9.0,';')", " 0X8.P-3;"}, + {"(RU,EX9.0,';')", " 0X9.P-3;"}, + {"(RD,EX9.0,';')", " 0X8.P-3;"}, + {"(RZ,EX9.0,';')", " 0X8.P-3;"}, + {"(RC,EX9.0,';')", " 0X9.P-3;"}, + }}, + {// EX rounding + 0xbff1000000000000uLL, // -1.0625 + { + {"(F7.4,';')", "-1.0625;"}, + {"(EX9.1,';')", "-0X8.8P-3;"}, + {"(EX9.0,';')", " -0X8.P-3;"}, + {"(RN,EX9.0,';')", " -0X8.P-3;"}, + {"(RU,EX9.0,';')", " -0X8.P-3;"}, + {"(RD,EX9.0,';')", " -0X9.P-3;"}, + {"(RZ,EX9.0,';')", " -0X8.P-3;"}, + {"(RC,EX9.0,';')", " -0X9.P-3;"}, }}, }; @@ -775,11 +818,11 @@ TEST(IOApiTests, FormatIntegerValues) { } //------------------------------------------------------------------------------ -/// Tests for input formatting real values +/// Tests for input editing real values //------------------------------------------------------------------------------ // Ensure double input values correctly map to raw uint64 values -TEST(IOApiTests, FormatDoubleInputValues) { +TEST(IOApiTests, EditDoubleInputValues) { using TestCaseTy = std::tuple; static const std::vector testCases{ {"(F18.0)", " 0", 0x0}, @@ -806,6 +849,21 @@ TEST(IOApiTests, FormatDoubleInputValues) { {"(BZ,F18.0)", " . ", 0x0}, {"(BZ,F18.0)", " . e +1 ", 0x0}, {"(DC,F18.0)", " 12,5", 0x4029000000000000}, + {"(EX22.0)", "0X0P0 ", 0x0}, // +0. + {"(EX22.0)", "-0X0P0 ", 0x8000000000000000}, // -0. + {"(EX22.0)", "0X.8P1 ", 0x3ff0000000000000}, // 1.0 + {"(EX22.0)", "0X8.P-3 ", 0x3ff0000000000000}, // 1.0 + {"(EX22.0)", "0X.1P4 ", 0x3ff0000000000000}, // 1.0 + {"(EX22.0)", "0X10.P-4 ", 0x3ff0000000000000}, // 1.0 + {"(EX22.0)", "0X8.00P-3 ", 0x3ff0000000000000}, // 1.0 + {"(EX22.0)", "0X80.0P-6 ", 0x4000000000000000}, // 2.0 + {"(EX22.0)", "0XC.CCCCCCCCCCCDP-7 ", 0x3fb999999999999a}, // 0.1 + {"(EX22.0)", "0X.8P-1021 ", 0x0010000000000000}, // min normal + {"(EX22.0)", "0X.8P-1022 ", 0x0008000000000000}, // subnormal + {"(EX22.0)", "0X.8P-1073 ", 0x0000000000000001}, // min subn. + {"(EX22.0)", "0X.FFFFFFFFFFFFF8P1024", 0x7fefffffffffffff}, // max finite + {"(EX22.0)", "0X.8P1025 ", 0x7ff0000000000000}, // +Inf + {"(EX22.0)", "-0X.8P1025 ", 0xfff0000000000000}, // -Inf }; for (auto const &[format, data, want] : testCases) { auto cookie{IONAME(BeginInternalFormattedInput)(