Skip to content

Commit 0890611

Browse files
klauslerjustinfargnoli
authored andcommitted
[flang][runtime] Better real MOD/MODULO results (llvm#77167)
The Fortran standard defines real MOD and MODULO with expressions like MOD(a,p) = a - AINT(a/p)*p. Unfortunately, these definitions have poor accuracy when a is much larger in magnitude than p, and every Fortran compiler uses better algorithms instead. Fixes llvm-test-suite/Fortran/gfortran/regression/mod_large_1.f90.
1 parent cd4e2e8 commit 0890611

File tree

5 files changed

+107
-58
lines changed

5 files changed

+107
-58
lines changed

flang/docs/Extensions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ end
102102
in F'2023 subclause 19.4 paragraphs 6 & 8 should apply. Since this
103103
compiler properly scopes these names, violations of these restrictions
104104
elicit only portability warnings by default.
105+
* The standard defines the intrinsic functions `MOD` and `MODULO`
106+
for real arguments using expressions in terms of `AINT` and `FLOOR`.
107+
These definitions yield fairly poor results due to floating-point
108+
cancellation, and every Fortran compiler (including this one)
109+
uses better algorithms.
105110

106111
## Extensions, deletions, and legacy features supported by default
107112

flang/include/flang/Evaluate/real.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ class Real : public common::RealDetails<PREC> {
170170
// DIM(X,Y) = MAX(X-Y, 0)
171171
ValueWithRealFlags<Real> DIM(const Real &,
172172
Rounding rounding = TargetCharacteristics::defaultRounding) const;
173-
// MOD(x,y) = x - AINT(x/y)*y
174-
// MODULO(x,y) = x - FLOOR(x/y)*y
173+
// MOD(x,y) = x - AINT(x/y)*y (in the standard)
174+
// MODULO(x,y) = x - FLOOR(x/y)*y (in the standard)
175175
ValueWithRealFlags<Real> MOD(const Real &,
176176
Rounding rounding = TargetCharacteristics::defaultRounding) const;
177177
ValueWithRealFlags<Real> MODULO(const Real &,

flang/lib/Evaluate/real.cpp

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -401,48 +401,59 @@ ValueWithRealFlags<Real<W, P>> Real<W, P>::HYPOT(
401401
return result;
402402
}
403403

404-
// MOD(x,y) = x - AINT(x/y)*y
404+
// MOD(x,y) = x - AINT(x/y)*y in the standard; unfortunately, this definition
405+
// can be pretty inaccurate when x is much larger than y in magnitude due to
406+
// cancellation. Implement instead with (essentially) arbitrary precision
407+
// long division, discarding the quotient and returning the remainder.
408+
// See runtime/numeric.cpp for more details.
405409
template <typename W, int P>
406410
ValueWithRealFlags<Real<W, P>> Real<W, P>::MOD(
407-
const Real &y, Rounding rounding) const {
411+
const Real &p, Rounding rounding) const {
408412
ValueWithRealFlags<Real> result;
409-
auto quotient{Divide(y, rounding)};
410-
if (quotient.value.IsInfinite() && IsFinite() && y.IsFinite() &&
411-
!y.IsZero()) {
412-
// x/y overflowed -- so it must be an integer in this representation and
413-
// the result must be a zero.
413+
if (IsNotANumber() || p.IsNotANumber() || IsInfinite()) {
414+
result.flags.set(RealFlag::InvalidArgument);
415+
result.value = NotANumber();
416+
} else if (p.IsZero()) {
417+
result.flags.set(RealFlag::DivideByZero);
418+
result.value = NotANumber();
419+
} else if (p.IsInfinite()) {
420+
result.value = *this;
421+
} else {
422+
result.value = ABS();
423+
auto pAbs{p.ABS()};
424+
Real half, adj;
425+
half.Normalize(false, exponentBias - 1, Fraction::MASKL(1)); // 0.5
426+
for (adj.Normalize(false, Exponent(), pAbs.GetFraction());
427+
result.value.Compare(pAbs) != Relation::Less;
428+
adj = adj.Multiply(half).value) {
429+
if (result.value.Compare(adj) != Relation::Less) {
430+
result.value =
431+
result.value.Subtract(adj, rounding).AccumulateFlags(result.flags);
432+
if (result.value.IsZero()) {
433+
break;
434+
}
435+
}
436+
}
414437
if (IsNegative()) {
415-
result.value = Real{}.Negate(); // -0.
438+
result.value = result.value.Negate();
416439
}
417-
} else {
418-
Real toInt{quotient.AccumulateFlags(result.flags)
419-
.ToWholeNumber(common::RoundingMode::ToZero)
420-
.AccumulateFlags(result.flags)};
421-
Real product{toInt.Multiply(y, rounding).AccumulateFlags(result.flags)};
422-
result.value = Subtract(product, rounding).AccumulateFlags(result.flags);
423440
}
424441
return result;
425442
}
426443

427-
// MODULO(x,y) = x - FLOOR(x/y)*y
444+
// MODULO(x,y) = x - FLOOR(x/y)*y in the standard; here, it is defined
445+
// in terms of MOD() with adjustment of the result.
428446
template <typename W, int P>
429447
ValueWithRealFlags<Real<W, P>> Real<W, P>::MODULO(
430-
const Real &y, Rounding rounding) const {
431-
ValueWithRealFlags<Real> result;
432-
auto quotient{Divide(y, rounding)};
433-
if (quotient.value.IsInfinite() && IsFinite() && y.IsFinite() &&
434-
!y.IsZero()) {
435-
// x/y overflowed -- so it must be an integer in this representation and
436-
// the result must be a zero.
437-
if (y.IsNegative()) {
438-
result.value = Real{}.Negate(); // -0.
448+
const Real &p, Rounding rounding) const {
449+
ValueWithRealFlags<Real> result{MOD(p, rounding)};
450+
if (IsNegative() != p.IsNegative()) {
451+
if (result.value.IsZero()) {
452+
result.value = result.value.Negate();
453+
} else {
454+
result.value =
455+
result.value.Add(p, rounding).AccumulateFlags(result.flags);
439456
}
440-
} else {
441-
Real toInt{quotient.AccumulateFlags(result.flags)
442-
.ToWholeNumber(common::RoundingMode::Down)
443-
.AccumulateFlags(result.flags)};
444-
Real product{toInt.Multiply(y, rounding).AccumulateFlags(result.flags)};
445-
result.value = Subtract(product, rounding).AccumulateFlags(result.flags);
446457
}
447458
return result;
448459
}

flang/runtime/numeric.cpp

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ template <typename T> inline RT_API_ATTRS T Fraction(T x) {
101101

102102
RT_DIAG_POP
103103

104+
// SET_EXPONENT (16.9.171)
105+
template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) {
106+
if (std::isnan(x)) {
107+
return x; // NaN -> same NaN
108+
} else if (std::isinf(x)) {
109+
return std::numeric_limits<T>::quiet_NaN(); // +/-Inf -> NaN
110+
} else if (x == 0) {
111+
return x; // return negative zero if x is negative zero
112+
} else {
113+
int expo{std::ilogb(x) + 1};
114+
auto ip{static_cast<int>(p - expo)};
115+
if (ip != p - expo) {
116+
ip = p < 0 ? std::numeric_limits<int>::min()
117+
: std::numeric_limits<int>::max();
118+
}
119+
return std::ldexp(x, ip); // x*2**(p-e)
120+
}
121+
}
122+
104123
// MOD & MODULO (16.9.135, .136)
105124
template <bool IS_MODULO, typename T>
106125
inline RT_API_ATTRS T IntMod(T x, T p, const char *sourceFile, int sourceLine) {
@@ -121,14 +140,43 @@ inline RT_API_ATTRS T RealMod(
121140
Terminator{sourceFile, sourceLine}.Crash(
122141
IS_MODULO ? "MODULO with P==0" : "MOD with P==0");
123142
}
124-
T quotient{a / p};
125-
if (std::isinf(quotient) && std::isfinite(a) && std::isfinite(p)) {
126-
// a/p overflowed -- so it must be an integer, and the result
127-
// must be a zero of the same sign as one of the operands.
128-
return std::copysign(T{}, IS_MODULO ? p : a);
143+
if (std::isnan(a) || std::isnan(p) || std::isinf(a)) {
144+
return std::numeric_limits<T>::quiet_NaN();
145+
} else if (std::isinf(p)) {
146+
return a;
147+
} else {
148+
// The standard defines MOD(a,p)=a-AINT(a/p)*p and
149+
// MODULO(a,p)=a-FLOOR(a/p)*p, but those definitions lose
150+
// precision badly due to cancellation when ABS(a) is
151+
// much larger than ABS(p).
152+
// Insights:
153+
// - MOD(a,p)=MOD(a-n*p,p) when a>0, p>0, integer n>0, and a>=n*p
154+
// - when n is a power of two, n*p is exact
155+
// - as a>=n*p, a-n*p does not round.
156+
// So repeatedly reduce a by all n*p in decreasing order of n;
157+
// what's left is the desired remainder. This is basically
158+
// the same algorithm as arbitrary precision binary long division,
159+
// discarding the quotient.
160+
T tmp{std::abs(a)};
161+
T pAbs{std::abs(p)};
162+
for (T adj{SetExponent(pAbs, Exponent<int>(tmp))}; tmp >= pAbs; adj /= 2) {
163+
if (tmp >= adj) {
164+
tmp -= adj;
165+
if (tmp == 0) {
166+
break;
167+
}
168+
}
169+
}
170+
if (a < 0) {
171+
tmp = -tmp;
172+
}
173+
if constexpr (IS_MODULO) {
174+
if ((a < 0) != (p < 0)) {
175+
tmp += p;
176+
}
177+
}
178+
return tmp;
129179
}
130-
T toInt{IS_MODULO ? std::floor(quotient) : std::trunc(quotient)};
131-
return a - toInt * p;
132180
}
133181

134182
// RRSPACING (16.9.164)
@@ -222,25 +270,6 @@ inline RT_API_ATTRS CppTypeFor<TypeCategory::Integer, 4> SelectedRealKind(
222270
return error ? error : kind;
223271
}
224272

225-
// SET_EXPONENT (16.9.171)
226-
template <typename T> inline RT_API_ATTRS T SetExponent(T x, std::int64_t p) {
227-
if (std::isnan(x)) {
228-
return x; // NaN -> same NaN
229-
} else if (std::isinf(x)) {
230-
return std::numeric_limits<T>::quiet_NaN(); // +/-Inf -> NaN
231-
} else if (x == 0) {
232-
return x; // return negative zero if x is negative zero
233-
} else {
234-
int expo{std::ilogb(x) + 1};
235-
auto ip{static_cast<int>(p - expo)};
236-
if (ip != p - expo) {
237-
ip = p < 0 ? std::numeric_limits<int>::min()
238-
: std::numeric_limits<int>::max();
239-
}
240-
return std::ldexp(x, ip); // x*2**(p-e)
241-
}
242-
}
243-
244273
// SPACING (16.9.180)
245274
template <int PREC, typename T> inline RT_API_ATTRS T Spacing(T x) {
246275
if (std::isnan(x)) {

flang/test/Evaluate/fold-mod.f90

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,8 @@ module m1
3939
logical, parameter :: test_modulo_r12b = sign(1., modulo(huge(0.), -tiny(0.))) == -1.
4040
logical, parameter :: test_modulo_r13a = modulo(huge(0.), tiny(0.)) == 0.
4141
logical, parameter :: test_modulo_r13b = sign(1., modulo(-huge(0.), -tiny(0.))) == -1.
42+
43+
logical, parameter :: test_mod_r14 = mod(1e22, 1.7) == .99592876
44+
logical, parameter :: test_modulo_r14 = modulo(1e22, -1.7) == -.7040713
45+
4246
end module

0 commit comments

Comments
 (0)