cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
565 lines
17 KiB
C++
565 lines
17 KiB
C++
//===-- lib/Decimal/decimal-to-binary.cpp ---------------------------------===//
|
|
//
|
|
// 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 "big-radix-floating-point.h"
|
|
#include "flang/Common/bit-population-count.h"
|
|
#include "flang/Common/leading-zero-bit-count.h"
|
|
#include "flang/Decimal/binary-floating-point.h"
|
|
#include "flang/Decimal/decimal.h"
|
|
#include "flang/Runtime/freestanding-tools.h"
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
#include <utility>
|
|
|
|
// Some environments, viz. glibc 2.17 and *BSD, allow the macro HUGE
|
|
// to leak out of <math.h>.
|
|
#undef HUGE
|
|
|
|
namespace Fortran::decimal {
|
|
|
|
template <int PREC, int LOG10RADIX>
|
|
bool BigRadixFloatingPointNumber<PREC, LOG10RADIX>::ParseNumber(
|
|
const char *&p, bool &inexact, const char *end) {
|
|
SetToZero();
|
|
if (end && p >= end) {
|
|
return false;
|
|
}
|
|
// Skip leading spaces
|
|
for (; p != end && *p == ' '; ++p) {
|
|
}
|
|
if (p == end) {
|
|
return false;
|
|
}
|
|
const char *q{p};
|
|
isNegative_ = *q == '-';
|
|
if (*q == '-' || *q == '+') {
|
|
++q;
|
|
}
|
|
const char *start{q};
|
|
for (; q != end && *q == '0'; ++q) {
|
|
}
|
|
const char *firstDigit{q};
|
|
for (; q != end && *q >= '0' && *q <= '9'; ++q) {
|
|
}
|
|
const char *point{nullptr};
|
|
if (q != end && *q == '.') {
|
|
point = q;
|
|
for (++q; q != end && *q >= '0' && *q <= '9'; ++q) {
|
|
}
|
|
}
|
|
if (q == start || (q == start + 1 && start == point)) {
|
|
return false; // require at least one digit
|
|
}
|
|
// There's a valid number here; set the reference argument to point to
|
|
// the first character afterward, which might be an exponent part.
|
|
p = q;
|
|
// Strip off trailing zeroes
|
|
if (point) {
|
|
while (q[-1] == '0') {
|
|
--q;
|
|
}
|
|
if (q[-1] == '.') {
|
|
point = nullptr;
|
|
--q;
|
|
}
|
|
}
|
|
if (!point) {
|
|
while (q > firstDigit && q[-1] == '0') {
|
|
--q;
|
|
++exponent_;
|
|
}
|
|
}
|
|
// Trim any excess digits
|
|
const char *limit{firstDigit + maxDigits * log10Radix + (point != nullptr)};
|
|
if (q > limit) {
|
|
inexact = true;
|
|
if (point >= limit) {
|
|
q = point;
|
|
point = nullptr;
|
|
}
|
|
if (!point) {
|
|
exponent_ += q - limit;
|
|
}
|
|
q = limit;
|
|
}
|
|
if (point) {
|
|
exponent_ -= static_cast<int>(q - point - 1);
|
|
}
|
|
if (q == firstDigit) {
|
|
exponent_ = 0; // all zeros
|
|
}
|
|
// Rack the decimal digits up into big Digits.
|
|
for (auto times{radix}; q-- > firstDigit;) {
|
|
if (*q != '.') {
|
|
if (times == radix) {
|
|
digit_[digits_++] = *q - '0';
|
|
times = 10;
|
|
} else {
|
|
digit_[digits_ - 1] += times * (*q - '0');
|
|
times *= 10;
|
|
}
|
|
}
|
|
}
|
|
// Look for an optional exponent field.
|
|
if (p == end) {
|
|
return true;
|
|
}
|
|
q = p;
|
|
switch (*q) {
|
|
case 'e':
|
|
case 'E':
|
|
case 'd':
|
|
case 'D':
|
|
case 'q':
|
|
case 'Q': {
|
|
if (++q == end) {
|
|
break;
|
|
}
|
|
bool negExpo{*q == '-'};
|
|
if (*q == '-' || *q == '+') {
|
|
++q;
|
|
}
|
|
if (q != end && *q >= '0' && *q <= '9') {
|
|
int expo{0};
|
|
for (; q != end && *q == '0'; ++q) {
|
|
}
|
|
const char *expDig{q};
|
|
for (; q != end && *q >= '0' && *q <= '9'; ++q) {
|
|
expo = 10 * expo + *q - '0';
|
|
}
|
|
if (q >= expDig + 8) {
|
|
// There's a ridiculous number of nonzero exponent digits.
|
|
// The decimal->binary conversion routine will cope with
|
|
// returning 0 or Inf, but we must ensure that "expo" didn't
|
|
// overflow back around to something legal.
|
|
expo = 10 * Real::decimalRange;
|
|
exponent_ = 0;
|
|
}
|
|
p = q; // exponent is valid; advance the termination pointer
|
|
if (negExpo) {
|
|
exponent_ -= expo;
|
|
} else {
|
|
exponent_ += expo;
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <int PREC, int LOG10RADIX>
|
|
void BigRadixFloatingPointNumber<PREC,
|
|
LOG10RADIX>::LoseLeastSignificantDigit() {
|
|
Digit LSD{digit_[0]};
|
|
for (int j{0}; j < digits_ - 1; ++j) {
|
|
digit_[j] = digit_[j + 1];
|
|
}
|
|
digit_[digits_ - 1] = 0;
|
|
bool incr{false};
|
|
switch (rounding_) {
|
|
case RoundNearest:
|
|
incr = LSD > radix / 2 || (LSD == radix / 2 && digit_[0] % 2 != 0);
|
|
break;
|
|
case RoundUp:
|
|
incr = LSD > 0 && !isNegative_;
|
|
break;
|
|
case RoundDown:
|
|
incr = LSD > 0 && isNegative_;
|
|
break;
|
|
case RoundToZero:
|
|
break;
|
|
case RoundCompatible:
|
|
incr = LSD >= radix / 2;
|
|
break;
|
|
}
|
|
for (int j{0}; (digit_[j] += incr) == radix; ++j) {
|
|
digit_[j] = 0;
|
|
}
|
|
}
|
|
|
|
// This local utility class represents an unrounded nonnegative
|
|
// binary floating-point value with an unbiased (i.e., signed)
|
|
// binary exponent, an integer value (not a fraction) with an implied
|
|
// binary point to its *right*, and some guard bits for rounding.
|
|
template <int PREC> class IntermediateFloat {
|
|
public:
|
|
static constexpr int precision{PREC};
|
|
using IntType = common::HostUnsignedIntType<precision>;
|
|
static constexpr IntType topBit{IntType{1} << (precision - 1)};
|
|
static constexpr IntType mask{topBit + (topBit - 1)};
|
|
|
|
RT_API_ATTRS IntermediateFloat() {}
|
|
IntermediateFloat(const IntermediateFloat &) = default;
|
|
|
|
// Assumes that exponent_ is valid on entry, and may increment it.
|
|
// Returns the number of guard_ bits that have been determined.
|
|
template <typename UINT> RT_API_ATTRS bool SetTo(UINT n) {
|
|
static constexpr int nBits{CHAR_BIT * sizeof n};
|
|
if constexpr (precision >= nBits) {
|
|
value_ = n;
|
|
guard_ = 0;
|
|
return 0;
|
|
} else {
|
|
int shift{common::BitsNeededFor(n) - precision};
|
|
if (shift <= 0) {
|
|
value_ = n;
|
|
guard_ = 0;
|
|
return 0;
|
|
} else {
|
|
value_ = n >> shift;
|
|
exponent_ += shift;
|
|
n <<= nBits - shift;
|
|
guard_ = (n >> (nBits - guardBits)) | ((n << guardBits) != 0);
|
|
return shift;
|
|
}
|
|
}
|
|
}
|
|
|
|
RT_API_ATTRS void ShiftIn(int bit = 0) { value_ = value_ + value_ + bit; }
|
|
RT_API_ATTRS bool IsFull() const { return value_ >= topBit; }
|
|
RT_API_ATTRS void AdjustExponent(int by) { exponent_ += by; }
|
|
RT_API_ATTRS void SetGuard(int g) {
|
|
guard_ |= (static_cast<GuardType>(g & 6) << (guardBits - 3)) | (g & 1);
|
|
}
|
|
|
|
RT_API_ATTRS ConversionToBinaryResult<PREC> ToBinary(
|
|
bool isNegative, FortranRounding) const;
|
|
|
|
private:
|
|
static constexpr int guardBits{3}; // guard, round, sticky
|
|
using GuardType = int;
|
|
static constexpr GuardType oneHalf{GuardType{1} << (guardBits - 1)};
|
|
|
|
IntType value_{0};
|
|
GuardType guard_{0};
|
|
int exponent_{0};
|
|
};
|
|
|
|
// The standard says that these overflow cases round to "representable"
|
|
// numbers, and some popular compilers interpret that to mean +/-HUGE()
|
|
// rather than +/-Inf.
|
|
static inline RT_API_ATTRS constexpr bool RoundOverflowToHuge(
|
|
enum FortranRounding rounding, bool isNegative) {
|
|
return rounding == RoundToZero || (!isNegative && rounding == RoundDown) ||
|
|
(isNegative && rounding == RoundUp);
|
|
}
|
|
|
|
template <int PREC>
|
|
ConversionToBinaryResult<PREC> IntermediateFloat<PREC>::ToBinary(
|
|
bool isNegative, FortranRounding rounding) const {
|
|
using Binary = BinaryFloatingPointNumber<PREC>;
|
|
// Create a fraction with a binary point to the left of the integer
|
|
// value_, and bias the exponent.
|
|
IntType fraction{value_};
|
|
GuardType guard{guard_};
|
|
int expo{exponent_ + Binary::exponentBias + (precision - 1)};
|
|
while (expo < 1 && (fraction > 0 || guard > oneHalf)) {
|
|
guard = (guard & 1) | (guard >> 1) |
|
|
((static_cast<GuardType>(fraction) & 1) << (guardBits - 1));
|
|
fraction >>= 1;
|
|
++expo;
|
|
}
|
|
int flags{Exact};
|
|
if (guard != 0) {
|
|
flags |= Inexact;
|
|
}
|
|
if (fraction == 0) {
|
|
if (guard <= oneHalf) {
|
|
if ((!isNegative && rounding == RoundUp) ||
|
|
(isNegative && rounding == RoundDown)) {
|
|
// round to least nonzero value
|
|
expo = 0;
|
|
} else { // round to zero
|
|
if (guard != 0) {
|
|
flags |= Underflow;
|
|
}
|
|
Binary zero;
|
|
if (isNegative) {
|
|
zero.Negate();
|
|
}
|
|
return {
|
|
std::move(zero), static_cast<enum ConversionResultFlags>(flags)};
|
|
}
|
|
}
|
|
} else {
|
|
// The value is nonzero; normalize it.
|
|
while (fraction < topBit && expo > 1) {
|
|
--expo;
|
|
fraction = fraction * 2 + (guard >> (guardBits - 2));
|
|
guard =
|
|
(((guard >> (guardBits - 2)) & 1) << (guardBits - 1)) | (guard & 1);
|
|
}
|
|
}
|
|
// Apply rounding
|
|
bool incr{false};
|
|
switch (rounding) {
|
|
case RoundNearest:
|
|
incr = guard > oneHalf || (guard == oneHalf && (fraction & 1));
|
|
break;
|
|
case RoundUp:
|
|
incr = guard != 0 && !isNegative;
|
|
break;
|
|
case RoundDown:
|
|
incr = guard != 0 && isNegative;
|
|
break;
|
|
case RoundToZero:
|
|
break;
|
|
case RoundCompatible:
|
|
incr = guard >= oneHalf;
|
|
break;
|
|
}
|
|
if (incr) {
|
|
if (fraction == mask) {
|
|
// rounding causes a carry
|
|
++expo;
|
|
fraction = topBit;
|
|
} else {
|
|
++fraction;
|
|
}
|
|
}
|
|
if (expo == 1 && fraction < topBit) {
|
|
expo = 0; // subnormal
|
|
flags |= Underflow;
|
|
} else if (expo == 0) {
|
|
flags |= Underflow;
|
|
} else if (expo >= Binary::maxExponent) {
|
|
if (RoundOverflowToHuge(rounding, isNegative)) {
|
|
expo = Binary::maxExponent - 1;
|
|
fraction = mask;
|
|
} else { // Inf
|
|
expo = Binary::maxExponent;
|
|
flags |= Overflow;
|
|
if constexpr (Binary::bits == 80) { // x87
|
|
fraction = IntType{1} << 63;
|
|
} else {
|
|
fraction = 0;
|
|
}
|
|
}
|
|
}
|
|
using Raw = typename Binary::RawType;
|
|
Raw raw = static_cast<Raw>(isNegative) << (Binary::bits - 1);
|
|
raw |= static_cast<Raw>(expo) << Binary::significandBits;
|
|
if constexpr (Binary::isImplicitMSB) {
|
|
fraction &= ~topBit;
|
|
}
|
|
raw |= fraction;
|
|
return {Binary(raw), static_cast<enum ConversionResultFlags>(flags)};
|
|
}
|
|
|
|
template <int PREC, int LOG10RADIX>
|
|
ConversionToBinaryResult<PREC>
|
|
BigRadixFloatingPointNumber<PREC, LOG10RADIX>::ConvertToBinary() {
|
|
// On entry, *this holds a multi-precision integer value in a radix of a
|
|
// large power of ten. Its radix point is defined to be to the right of its
|
|
// digits, and "exponent_" is the power of ten by which it is to be scaled.
|
|
Normalize();
|
|
if (digits_ == 0) { // zero value
|
|
return {Real{SignBit()}};
|
|
}
|
|
// The value is not zero: x = D. * 10.**E
|
|
// Shift our perspective on the radix (& decimal) point so that
|
|
// it sits to the *left* of the digits: i.e., x = .D * 10.**E
|
|
exponent_ += digits_ * log10Radix;
|
|
// Sanity checks for ridiculous exponents
|
|
static constexpr int crazy{2 * Real::decimalRange + log10Radix};
|
|
if (exponent_ < -crazy) {
|
|
enum ConversionResultFlags flags {
|
|
static_cast<enum ConversionResultFlags>(Inexact | Underflow)
|
|
};
|
|
if ((!isNegative_ && rounding_ == RoundUp) ||
|
|
(isNegative_ && rounding_ == RoundDown)) {
|
|
// return least nonzero value
|
|
return {Real{Raw{1} | SignBit()}, flags};
|
|
} else { // underflow to +/-0.
|
|
return {Real{SignBit()}, flags};
|
|
}
|
|
} else if (exponent_ > crazy) { // overflow to +/-HUGE() or +/-Inf
|
|
if (RoundOverflowToHuge(rounding_, isNegative_)) {
|
|
return {Real{HUGE()}};
|
|
} else {
|
|
return {Real{Infinity()}, Overflow};
|
|
}
|
|
}
|
|
// Apply any negative decimal exponent by multiplication
|
|
// by a power of two, adjusting the binary exponent to compensate.
|
|
IntermediateFloat<PREC> f;
|
|
while (exponent_ < log10Radix) {
|
|
// x = 0.D * 10.**E * 2.**(f.ex) -> 512 * 0.D * 10.**E * 2.**(f.ex-9)
|
|
f.AdjustExponent(-9);
|
|
digitLimit_ = digits_;
|
|
if (int carry{MultiplyWithoutNormalization<512>()}) {
|
|
// x = c.D * 10.**E * 2.**(f.ex) -> .cD * 10.**(E+16) * 2.**(f.ex)
|
|
PushCarry(carry);
|
|
exponent_ += log10Radix;
|
|
}
|
|
}
|
|
// Apply any positive decimal exponent greater than
|
|
// is needed to treat the topmost digit as an integer
|
|
// part by multiplying by 10 or 10000 repeatedly.
|
|
while (exponent_ > log10Radix) {
|
|
digitLimit_ = digits_;
|
|
int carry;
|
|
if (exponent_ >= log10Radix + 4) {
|
|
// x = 0.D * 10.**E * 2.**(f.ex) -> 625 * .D * 10.**(E-4) * 2.**(f.ex+4)
|
|
exponent_ -= 4;
|
|
carry = MultiplyWithoutNormalization<(5 * 5 * 5 * 5)>();
|
|
f.AdjustExponent(4);
|
|
} else {
|
|
// x = 0.D * 10.**E * 2.**(f.ex) -> 5 * .D * 10.**(E-1) * 2.**(f.ex+1)
|
|
--exponent_;
|
|
carry = MultiplyWithoutNormalization<5>();
|
|
f.AdjustExponent(1);
|
|
}
|
|
if (carry != 0) {
|
|
// x = c.D * 10.**E * 2.**(f.ex) -> .cD * 10.**(E+16) * 2.**(f.ex)
|
|
PushCarry(carry);
|
|
exponent_ += log10Radix;
|
|
}
|
|
}
|
|
// So exponent_ is now log10Radix, meaning that the
|
|
// MSD can be taken as an integer part and transferred
|
|
// to the binary result.
|
|
// x = .jD * 10.**16 * 2.**(f.ex) -> .D * j * 2.**(f.ex)
|
|
int guardShift{f.SetTo(digit_[--digits_])};
|
|
// Transfer additional bits until the result is normal.
|
|
digitLimit_ = digits_;
|
|
while (!f.IsFull()) {
|
|
// x = ((b.D)/2) * j * 2.**(f.ex) -> .D * (2j + b) * 2.**(f.ex-1)
|
|
f.AdjustExponent(-1);
|
|
std::uint32_t carry = MultiplyWithoutNormalization<2>();
|
|
f.ShiftIn(carry);
|
|
}
|
|
// Get the next few bits for rounding. Allow for some guard bits
|
|
// that may have already been set in f.SetTo() above.
|
|
int guard{0};
|
|
if (guardShift == 0) {
|
|
guard = MultiplyWithoutNormalization<4>();
|
|
} else if (guardShift == 1) {
|
|
guard = MultiplyWithoutNormalization<2>();
|
|
}
|
|
guard = guard + guard + !IsZero();
|
|
f.SetGuard(guard);
|
|
return f.ToBinary(isNegative_, rounding_);
|
|
}
|
|
|
|
template <int PREC, int LOG10RADIX>
|
|
ConversionToBinaryResult<PREC>
|
|
BigRadixFloatingPointNumber<PREC, LOG10RADIX>::ConvertToBinary(
|
|
const char *&p, const char *limit) {
|
|
bool inexact{false};
|
|
if (ParseNumber(p, inexact, limit)) {
|
|
auto result{ConvertToBinary()};
|
|
if (inexact) {
|
|
result.flags =
|
|
static_cast<enum ConversionResultFlags>(result.flags | Inexact);
|
|
}
|
|
return result;
|
|
} else {
|
|
// Could not parse a decimal floating-point number. p has been
|
|
// advanced over any leading spaces. Most Fortran compilers set
|
|
// the sign bit for -NaN.
|
|
const char *q{p};
|
|
if (!limit || q < limit) {
|
|
isNegative_ = *q == '-';
|
|
if (isNegative_ || *q == '+') {
|
|
++q;
|
|
}
|
|
}
|
|
if ((!limit || limit >= q + 3) && runtime::toupper(q[0]) == 'N' &&
|
|
runtime::toupper(q[1]) == 'A' && runtime::toupper(q[2]) == 'N') {
|
|
// NaN
|
|
p = q + 3;
|
|
bool isQuiet{true};
|
|
if ((!limit || p < limit) && *p == '(') {
|
|
int depth{1};
|
|
do {
|
|
++p;
|
|
if (limit && p >= limit) {
|
|
// Invalid input
|
|
return {Real{NaN(false)}, Invalid};
|
|
} else if (*p == '(') {
|
|
++depth;
|
|
} else if (*p == ')') {
|
|
--depth;
|
|
} else if (*p != ' ') {
|
|
// Implementation dependent, but other compilers
|
|
// all return quiet NaNs.
|
|
}
|
|
} while (depth > 0);
|
|
++p;
|
|
}
|
|
return {Real{NaN(isQuiet)}};
|
|
} else { // Inf?
|
|
if ((!limit || limit >= q + 3) && runtime::toupper(q[0]) == 'I' &&
|
|
runtime::toupper(q[1]) == 'N' && runtime::toupper(q[2]) == 'F') {
|
|
if ((!limit || limit >= q + 8) && runtime::toupper(q[3]) == 'I' &&
|
|
runtime::toupper(q[4]) == 'N' && runtime::toupper(q[5]) == 'I' &&
|
|
runtime::toupper(q[6]) == 'T' && runtime::toupper(q[7]) == 'Y') {
|
|
p = q + 8;
|
|
} else {
|
|
p = q + 3;
|
|
}
|
|
return {Real{Infinity()}};
|
|
} else {
|
|
// Invalid input
|
|
return {Real{NaN()}, Invalid};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <int PREC>
|
|
ConversionToBinaryResult<PREC> ConvertToBinary(
|
|
const char *&p, enum FortranRounding rounding, const char *end) {
|
|
return BigRadixFloatingPointNumber<PREC>{rounding}.ConvertToBinary(p, end);
|
|
}
|
|
|
|
template ConversionToBinaryResult<8> ConvertToBinary<8>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
template ConversionToBinaryResult<11> ConvertToBinary<11>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
template ConversionToBinaryResult<24> ConvertToBinary<24>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
template ConversionToBinaryResult<53> ConvertToBinary<53>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
template ConversionToBinaryResult<64> ConvertToBinary<64>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
template ConversionToBinaryResult<113> ConvertToBinary<113>(
|
|
const char *&, enum FortranRounding, const char *end);
|
|
|
|
extern "C" {
|
|
RT_EXT_API_GROUP_BEGIN
|
|
|
|
enum ConversionResultFlags ConvertDecimalToFloat(
|
|
const char **p, float *f, enum FortranRounding rounding) {
|
|
auto result{Fortran::decimal::ConvertToBinary<24>(*p, rounding)};
|
|
std::memcpy(reinterpret_cast<void *>(f),
|
|
reinterpret_cast<const void *>(&result.binary), sizeof *f);
|
|
return result.flags;
|
|
}
|
|
enum ConversionResultFlags ConvertDecimalToDouble(
|
|
const char **p, double *d, enum FortranRounding rounding) {
|
|
auto result{Fortran::decimal::ConvertToBinary<53>(*p, rounding)};
|
|
std::memcpy(reinterpret_cast<void *>(d),
|
|
reinterpret_cast<const void *>(&result.binary), sizeof *d);
|
|
return result.flags;
|
|
}
|
|
enum ConversionResultFlags ConvertDecimalToLongDouble(
|
|
const char **p, long double *ld, enum FortranRounding rounding) {
|
|
auto result{Fortran::decimal::ConvertToBinary<64>(*p, rounding)};
|
|
std::memcpy(reinterpret_cast<void *>(ld),
|
|
reinterpret_cast<const void *>(&result.binary), sizeof *ld);
|
|
return result.flags;
|
|
}
|
|
|
|
RT_EXT_API_GROUP_END
|
|
} // extern "C"
|
|
} // namespace Fortran::decimal
|