Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
native_compound_math.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "native_math.hpp"
4#include <array>
5
6namespace uni20::detail::native_math
7{
8// A bounded expansion used only by compound. Guard words cover amplification
9// by a signed-64-bit exponent: 4 words for binary32, 3 for binary64/80, 2 for
10// binary128. Components are nonoverlapping, in increasing magnitude order.
11// See docs/tensor/native_compound.md for the error model and acceptance tests.
12template <NativeReal R> class compound_expansion {
13 public:
14 void add(R value)
15 {
16 int size = 0;
17 for (int i = 0; i < size_; ++i)
18 {
19 R sum = value + terms_[i];
20 R part = sum - value;
21 R error = (value - (sum - part)) + (terms_[i] - part);
22 if (error != 0) terms_[size++] = error;
23 value = sum;
24 }
25 if (value != 0 || size == 0) terms_[size++] = value;
26 size_ = size;
27 }
28
29 static compound_expansion multiply(compound_expansion const& a, compound_expansion const& b)
30 {
31 compound_expansion result;
32 for (int i = 0; i < a.size_; ++i)
33 for (int j = 0; j < b.size_; ++j)
34 {
35 R product = a.terms_[i] * b.terms_[j];
36 result.add(std::fma(a.terms_[i], b.terms_[j], -product));
37 result.add(product);
38 }
39 result.compress();
40 return result;
41 }
42
43 // The leading component is positive. Scaling every word keeps the product
44 // arithmetic near unity even when the final power is outside native range.
45 int normalize()
46 {
47 int exponent;
48 std::frexp(this->high(), &exponent);
49 for (int i = 0; i < size_; ++i)
50 terms_[i] = std::ldexp(terms_[i], -exponent);
51 return exponent;
52 }
53
54 R rounded(bool reciprocal) const
55 {
56 if (!reciprocal)
57 {
58 R sum = 0;
59 for (int i = 0; i < size_; ++i)
60 sum += terms_[i];
61 return sum;
62 }
63 // One quotient correction suffices for a native result. Apply it before
64 // the binary scale, so an intermediate reciprocal cannot overflow.
65 R quotient = R(1) / this->high();
66 R residual = std::fma(-quotient, this->high(), R(1));
67 for (int i = size_ - 2; i >= 0; --i)
68 residual = std::fma(-quotient, terms_[i], residual);
69 return quotient + residual / this->high();
70 }
71
72 private:
73 static constexpr int digits_ = numeric_limits<R>::digits();
74 static constexpr int words_ = (digits_ + 72 + digits_ - 1) / digits_;
75 // A product adds at most two terms per pair of retained input words.
76 std::array<R, 2 * words_ * words_ + 2> terms_{};
77 int size_ = 0;
78
79 R high() const { return terms_[size_ - 1]; }
80
81 void compress()
82 {
83 // Two passes of FastTwoSum pack the expansion before discarding its
84 // smallest words. Truncating a sparse, uncompressed expansion would not
85 // retain the intended number of significand bits.
86 R carry = this->high();
87 int bottom = size_;
88 for (int i = size_ - 2; i >= 0; --i)
89 {
90 R sum = carry + terms_[i];
91 R error = terms_[i] - (sum - carry);
92 if (error != 0)
93 {
94 terms_[--bottom] = sum;
95 carry = error;
96 }
97 else
98 carry = sum;
99 }
100 int size = 0;
101 for (int i = bottom; i < size_; ++i)
102 {
103 R sum = terms_[i] + carry;
104 R error = carry - (sum - terms_[i]);
105 if (error != 0) terms_[size++] = error;
106 carry = sum;
107 }
108 terms_[size++] = carry;
109 size_ = size;
110 if (size_ > words_)
111 {
112 std::copy(terms_.begin() + size_ - words_, terms_.begin() + size_, terms_.begin());
113 size_ = words_;
114 }
115 }
116};
117
118template <NativeReal R> R portable_compound(R x, std::int64_t exponent)
119{
120 using L = numeric_limits<R>;
121 if (x < R(-1)) return L::quiet_NaN();
122 if (exponent == 0 || x == 0) return R(1);
123 if (std::isnan(x)) return x;
124 if (x == R(-1)) return exponent < 0 ? L::infinity() : R(0);
125 if (std::isinf(x)) return exponent > 0 ? L::infinity() : R(0);
126 if (exponent == 1) return R(1) + x;
127
128 compound_expansion<R> base;
129 base.add(R(1));
130 base.add(x);
131 int base_exponent = base.normalize();
132 compound_expansion<R> result;
133 result.add(R(0.5));
134 int result_exponent = 1;
135 // Unsigned subtraction handles INT64_MIN without signed negation.
136 auto remaining = exponent < 0 ? std::uint64_t(0) - std::uint64_t(exponent) : std::uint64_t(exponent);
137 constexpr int limit = 2 * (L::max_exponent - L::min_exponent + L::digits());
138 while (remaining)
139 {
140 if (remaining & 1)
141 {
142 result = compound_expansion<R>::multiply(result, base);
143 result_exponent += base_exponent + result.normalize();
144 }
145 // All positive powers of the base lie on the same side of one. Once a
146 // factor exceeds this conservative scale bound, any remaining power also
147 // exceeds the result range, including after a reciprocal. Checking here
148 // bounds both exponent accumulators before the next doubling/addition.
149 if (result_exponent > limit || base_exponent > limit) return exponent > 0 ? L::infinity() : R(0);
150 if (result_exponent < -limit || base_exponent < -limit) return exponent > 0 ? R(0) : L::infinity();
151 remaining >>= 1;
152 if (remaining)
153 {
154 base = compound_expansion<R>::multiply(base, base);
155 base_exponent = 2 * base_exponent + base.normalize();
156 }
157 }
158 return std::ldexp(result.rounded(exponent < 0), exponent < 0 ? -result_exponent : result_exponent);
159}
160
161template <NativeReal R> struct compound_math_provider
162{
163 static R evaluate(R x, std::int64_t exponent) { return portable_compound(x, exponent); }
164};
165#if UNI20_HAS_COMPOUNDN_FLOAT
166template <> struct compound_math_provider<float>
167{
168 static float evaluate(float x, std::int64_t exponent) { return ::compoundnf(x, exponent); }
169};
170#endif
171#if UNI20_HAS_COMPOUNDN_DOUBLE
172template <> struct compound_math_provider<double>
173{
174 static double evaluate(double x, std::int64_t exponent) { return ::compoundn(x, exponent); }
175};
176#endif
177#if UNI20_HAS_COMPOUNDN_LONG_DOUBLE
178template <> struct compound_math_provider<long double>
179{
180 static long double evaluate(long double x, std::int64_t exponent) { return ::compoundnl(x, exponent); }
181};
182#endif
183#if UNI20_HAS_FLOAT128 && UNI20_HAS_COMPOUNDN_FLOAT128
184template <> struct compound_math_provider<float128>
185{
186 static float128 evaluate(float128 x, std::int64_t exponent) { return ::compoundnf128(x, exponent); }
187};
188#endif
189
190template <NativeReal R, MathInteger I> R compound(R x, I exponent)
191{
192 return compound_math_provider<R>::evaluate(x, math_integer(exponent));
193}
194} // namespace uni20::detail::native_math
locale_t value
Definition data_table.cpp:49
T product(std::vector< T > const &values)
Definition mpreal_exact_example.cpp:13
Definition qnum.hpp:403
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
void normalize(Ops &ops, V &v, make_real_t< S > const &length, Precision p)
Definition gmres.hpp:139
constexpr detail::scalar_math::compound_fn compound
Integer power of one plus x, retaining the original increment.
Definition math.hpp:668
void sum(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a full sum into an explicit async scalar tensor.
Definition linalg/async/reductions.hpp:309
int add(int a, int b)
Definition dummy.cpp:6
double multiply(double a, double b)
Definition dummy.cpp:8