Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
native_airy_phase.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "native_math.hpp"
5#include <algorithm>
6#include <array>
7#include <bit>
8#include <cassert>
9#include <cstdint>
10#include <utility>
12
13namespace uni20::detail::native_math::airy_detail
14{
15template <int Capacity> struct UInt
16{
17 // Capacity follows the scalar exponent bound; only size limbs are active.
18 std::array<std::uint32_t, Capacity> w{};
19 int size = 0;
20 void trim()
21 {
22 while (size && !w[size - 1])
23 --size;
24 }
25 int bits() const { return size ? 32 * (size - 1) + 32 - std::countl_zero(w[size - 1]) : 0; }
26 bool bit(int k) const { return k >= 0 && k / 32 < size && ((w[k / 32] >> (k % 32)) & 1); }
27 void shift(int n)
28 {
29 std::uint64_t c = 0;
30 for (int i = 0; i < size; ++i)
31 {
32 auto t = (std::uint64_t(w[i]) << n) | c;
33 w[i] = std::uint32_t(t);
34 c = t >> 32;
35 }
36 if (c)
37 {
38 assert(size < Capacity);
39 w[size++] = c;
40 }
41 }
42 void add(unsigned n)
43 {
44 std::uint64_t c = n;
45 for (int i = 0; i < size; ++i)
46 {
47 auto t = std::uint64_t(w[i]) + c;
48 w[i] = std::uint32_t(t);
49 c = t >> 32;
50 if (!c) return;
51 }
52 if (c)
53 {
54 assert(size < Capacity);
55 w[size++] = c;
56 }
57 }
58 std::uint32_t trial_word(int i) const
59 {
60 return (i < size ? w[i] << 2 : 0u) | (i > 0 && i - 1 < size ? w[i - 1] >> 30 : 0u) | (i == 0 ? 1u : 0u);
61 }
62 bool subtract_trial(UInt const& root)
63 {
64 int n = root.size + (root.size && root.w[root.size - 1] >> 30 ? 1 : 0);
65 if (!n) n = 1;
66 bool ge = size >= n;
67 if (size == n)
68 {
69 for (int i = size - 1; i >= 0; --i)
70 {
71 auto b = root.trial_word(i);
72 if (w[i] != b)
73 {
74 ge = w[i] > b;
75 break;
76 }
77 }
78 }
79 if (!ge) return false;
80 std::uint64_t borrow = 0;
81 for (int i = 0; i < size; ++i)
82 {
83 std::uint64_t b = std::uint64_t(root.trial_word(i)) + borrow;
84 auto a = std::uint64_t(w[i]);
85 w[i] = std::uint32_t(a - b);
86 borrow = a < b;
87 }
88 assert(!borrow);
89 this->trim();
90 return true;
91 }
92 static UInt mul(UInt const& a, UInt const& b)
93 {
94 UInt r;
95 r.size = a.size + b.size;
96 assert(r.size <= Capacity);
97 for (int i = 0; i < a.size; ++i)
98 {
99 std::uint64_t c = 0;
100 for (int j = 0; j < b.size; ++j)
101 {
102 auto t = std::uint64_t(a.w[i]) * b.w[j] + r.w[i + j] + c;
103 r.w[i + j] = std::uint32_t(t);
104 c = t >> 32;
105 }
106 r.w[i + b.size] = c;
107 }
108 r.trim();
109 return r;
110 }
111};
112// Restoring binary square root of n*2^(2*shift), exactly rounded downward.
113// Reading zero bit pairs avoids allocating the shifted radicand.
114template <int Capacity> UInt<Capacity> root_scaled(UInt<Capacity> const& n, int shift)
115{
116 UInt<Capacity> root, rem;
117 int pairs = (n.bits() + 2 * shift + 1) / 2;
118 for (int i = pairs - 1; i >= 0; --i)
119 {
120 rem.shift(2);
121 rem.add(unsigned(n.bit(2 * i + 1 - 2 * shift)) * 2 + unsigned(n.bit(2 * i - 2 * shift)));
122 bool up = rem.subtract_trial(root);
123 root.shift(1);
124 if (up) root.add(1);
125 }
126 return root;
127}
128// Compute 4*x^(3/2)/(3*pi) modulo four from the original dyadic input.
129// Integer-root and constant truncation lose <2^(-digits-31) phase units.
130// The final native sin/cos sees only a reduced angle in [-pi/4,pi/4].
131template <NativeReal R, int Capacity> std::pair<R, R> phase_impl(R x)
132{
133 using Integer = UInt<Capacity>;
134 using L = uni20::numeric_limits<R>;
135 int exponent;
136 R f = std::frexp(x, &exponent);
137 Integer m;
138 for (int k = 0; k < L::digits(); ++k)
139 {
140 f *= 2;
141 m.shift(1);
142 if (f >= 1)
143 {
144 m.add(1);
145 f -= 1;
146 }
147 }
148 Integer n = Integer::mul(Integer::mul(m, m), m);
149 int e = 3 * (exponent - L::digits());
150 if (e % 2 != 0)
151 {
152 n.shift(1);
153 --e;
154 }
155 e /= 2;
156 constexpr int guard = L::digits() + 32;
157 int shift = std::max(0, e + guard);
158 Integer root = root_scaled(n, shift);
159 int constant_bits = ((3 * exponent / 2 + guard + 16 + 31) / 32) * 32;
160 assert(constant_bits > 0 && constant_bits <= phase_constant_bits);
161 Integer c;
162 c.size = constant_bits / 32;
163 for (int i = 0; i < constant_bits / 32; ++i)
164 c.w[i] = phase_constant[constant_bits / 32 - 1 - i];
165 Integer prod = Integer::mul(root, c);
166 int binary_point = shift - e + constant_bits;
167 int q = (unsigned(prod.bit(binary_point)) + 2 * unsigned(prod.bit(binary_point + 1)) + 1) % 4;
168 R frac = 0;
169 for (int k = L::digits() + 16; k >= 1; --k)
170 frac = (frac + R(prod.bit(binary_point - k))) / 2;
171 R angle = (frac - R(.5)) * (uni20::pi<R> / 2);
172 R s = std::sin(angle), co = std::cos(angle);
173 switch (q)
174 {
175 case 0:
176 return {s, co};
177 case 1:
178 return {co, -s};
179 case 2:
180 return {-s, -co};
181 default:
182 return {-co, s};
183 }
184}
185
186// Return sin(zeta+pi/4), cos(zeta+pi/4), zeta=2*x^(3/2)/3.
187// The small capacity avoids clearing a large frame for ordinary arguments.
188template <NativeReal R> std::pair<R, R> phase(R x)
189{
192 assert(x >= 4 && std::isfinite(x));
193 int e;
194 std::frexp(x, &e);
195 if (e <= 128) return phase_impl<R, 32>(x);
196 constexpr int max_bits = 3 * uni20::numeric_limits<R>::max_exponent / 2 + uni20::numeric_limits<R>::digits() + 64;
197 return phase_impl<R, 2 * ((max_bits + 31) / 32) + 8>(x);
198}
199} // namespace uni20::detail::native_math::airy_detail
std::string trim(const std::string &s)
Definition terminal.cpp:162
std::pair< int, int > size(std::FILE *stream)
Return the terminal size associated with a C stream.
Definition terminal.cpp:504
Definition cli.cpp:6
int add(int a, int b)
Definition dummy.cpp:6
Project-level numeric limits customization point.
Definition numeric_limits.hpp:16