xref: /llvm-project/pstl/include/pstl/internal/numeric_impl.h (revision 843c12d6a0cdfd64c5a92e24eb58ba9ee17ca1ee)
1 // -*- C++ -*-
2 //===----------------------------------------------------------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #ifndef _PSTL_NUMERIC_IMPL_H
11 #define _PSTL_NUMERIC_IMPL_H
12 
13 #include <iterator>
14 #include <type_traits>
15 #include <numeric>
16 
17 #include "parallel_backend.h"
18 #include "pstl_config.h"
19 #include "execution_impl.h"
20 #include "unseq_backend_simd.h"
21 #include "algorithm_fwd.h"
22 
23 _PSTL_HIDE_FROM_ABI_PUSH
24 
25 namespace __pstl
26 {
27 namespace __internal
28 {
29 
30 //------------------------------------------------------------------------
31 // transform_reduce (version with two binary functions, according to draft N4659)
32 //------------------------------------------------------------------------
33 
34 template <class _ForwardIterator1, class _ForwardIterator2, class _Tp, class _BinaryOperation1, class _BinaryOperation2>
35 _Tp
__brick_transform_reduce(_ForwardIterator1 __first1,_ForwardIterator1 __last1,_ForwardIterator2 __first2,_Tp __init,_BinaryOperation1 __binary_op1,_BinaryOperation2 __binary_op2,std::false_type)36 __brick_transform_reduce(_ForwardIterator1 __first1, _ForwardIterator1 __last1, _ForwardIterator2 __first2, _Tp __init,
37                          _BinaryOperation1 __binary_op1, _BinaryOperation2 __binary_op2,
38                          /*is_vector=*/std::false_type) noexcept
39 {
40     return std::inner_product(__first1, __last1, __first2, __init, __binary_op1, __binary_op2);
41 }
42 
43 template <class _RandomAccessIterator1, class _RandomAccessIterator2, class _Tp, class _BinaryOperation1,
44           class _BinaryOperation2>
45 _Tp
__brick_transform_reduce(_RandomAccessIterator1 __first1,_RandomAccessIterator1 __last1,_RandomAccessIterator2 __first2,_Tp __init,_BinaryOperation1 __binary_op1,_BinaryOperation2 __binary_op2,std::true_type)46 __brick_transform_reduce(_RandomAccessIterator1 __first1, _RandomAccessIterator1 __last1,
47                          _RandomAccessIterator2 __first2, _Tp __init, _BinaryOperation1 __binary_op1,
48                          _BinaryOperation2 __binary_op2,
49                          /*is_vector=*/std::true_type) noexcept
50 {
51     typedef typename std::iterator_traits<_RandomAccessIterator1>::difference_type _DifferenceType;
52     return __unseq_backend::__simd_transform_reduce(
53         __last1 - __first1, __init, __binary_op1,
54         [=, &__binary_op2](_DifferenceType __i) { return __binary_op2(__first1[__i], __first2[__i]); });
55 }
56 
57 template <class _Tag, class _ExecutionPolicy, class _ForwardIterator1, class _ForwardIterator2, class _Tp,
58           class _BinaryOperation1, class _BinaryOperation2>
59 _Tp
__pattern_transform_reduce(_Tag,_ExecutionPolicy &&,_ForwardIterator1 __first1,_ForwardIterator1 __last1,_ForwardIterator2 __first2,_Tp __init,_BinaryOperation1 __binary_op1,_BinaryOperation2 __binary_op2)60 __pattern_transform_reduce(_Tag, _ExecutionPolicy&&, _ForwardIterator1 __first1, _ForwardIterator1 __last1,
61                            _ForwardIterator2 __first2, _Tp __init, _BinaryOperation1 __binary_op1,
62                            _BinaryOperation2 __binary_op2) noexcept
63 {
64     return __brick_transform_reduce(__first1, __last1, __first2, __init, __binary_op1, __binary_op2,
65                                     typename _Tag::__is_vector{});
66 }
67 
68 template <class _IsVector, class _ExecutionPolicy, class _RandomAccessIterator1, class _RandomAccessIterator2,
69           class _Tp, class _BinaryOperation1, class _BinaryOperation2>
70 _Tp
__pattern_transform_reduce(__parallel_tag<_IsVector> __tag,_ExecutionPolicy && __exec,_RandomAccessIterator1 __first1,_RandomAccessIterator1 __last1,_RandomAccessIterator2 __first2,_Tp __init,_BinaryOperation1 __binary_op1,_BinaryOperation2 __binary_op2)71 __pattern_transform_reduce(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _RandomAccessIterator1 __first1,
72                            _RandomAccessIterator1 __last1, _RandomAccessIterator2 __first2, _Tp __init,
73                            _BinaryOperation1 __binary_op1, _BinaryOperation2 __binary_op2)
74 {
75     using __backend_tag = typename decltype(__tag)::__backend_tag;
76 
77     return __internal::__except_handler(
78         [&]()
79         {
80             return __par_backend::__parallel_transform_reduce(
81                 __backend_tag{}, std::forward<_ExecutionPolicy>(__exec), __first1, __last1,
82                 [__first1, __first2, __binary_op2](_RandomAccessIterator1 __i) mutable
83                 { return __binary_op2(*__i, *(__first2 + (__i - __first1))); },
84                 __init,
85                 __binary_op1, // Combine
86                 [__first1, __first2, __binary_op1, __binary_op2](_RandomAccessIterator1 __i, _RandomAccessIterator1 __j,
87                                                                  _Tp __init) -> _Tp
88                 {
89                     return __internal::__brick_transform_reduce(__i, __j, __first2 + (__i - __first1), __init,
90                                                                 __binary_op1, __binary_op2, _IsVector{});
91                 });
92         });
93 }
94 
95 //------------------------------------------------------------------------
96 // transform_reduce (version with unary and binary functions)
97 //------------------------------------------------------------------------
98 
99 template <class _ForwardIterator, class _Tp, class _BinaryOperation, class _UnaryOperation>
100 _Tp
__brick_transform_reduce(_ForwardIterator __first,_ForwardIterator __last,_Tp __init,_BinaryOperation __binary_op,_UnaryOperation __unary_op,std::false_type)101 __brick_transform_reduce(_ForwardIterator __first, _ForwardIterator __last, _Tp __init, _BinaryOperation __binary_op,
102                          _UnaryOperation __unary_op, /*is_vector=*/std::false_type) noexcept
103 {
104     return std::transform_reduce(__first, __last, __init, __binary_op, __unary_op);
105 }
106 
107 template <class _RandomAccessIterator, class _Tp, class _UnaryOperation, class _BinaryOperation>
108 _Tp
__brick_transform_reduce(_RandomAccessIterator __first,_RandomAccessIterator __last,_Tp __init,_BinaryOperation __binary_op,_UnaryOperation __unary_op,std::true_type)109 __brick_transform_reduce(_RandomAccessIterator __first, _RandomAccessIterator __last, _Tp __init,
110                          _BinaryOperation __binary_op, _UnaryOperation __unary_op,
111                          /*is_vector=*/std::true_type) noexcept
112 {
113     typedef typename std::iterator_traits<_RandomAccessIterator>::difference_type _DifferenceType;
114     return __unseq_backend::__simd_transform_reduce(
115         __last - __first, __init, __binary_op,
116         [=, &__unary_op](_DifferenceType __i) { return __unary_op(__first[__i]); });
117 }
118 
119 template <class _Tag, class _ExecutionPolicy, class _ForwardIterator, class _Tp, class _BinaryOperation,
120           class _UnaryOperation>
121 _Tp
__pattern_transform_reduce(_Tag,_ExecutionPolicy &&,_ForwardIterator __first,_ForwardIterator __last,_Tp __init,_BinaryOperation __binary_op,_UnaryOperation __unary_op)122 __pattern_transform_reduce(_Tag, _ExecutionPolicy&&, _ForwardIterator __first, _ForwardIterator __last, _Tp __init,
123                            _BinaryOperation __binary_op, _UnaryOperation __unary_op) noexcept
124 {
125     return __internal::__brick_transform_reduce(__first, __last, __init, __binary_op, __unary_op,
126                                                 typename _Tag::__is_vector{});
127 }
128 
129 template <class _IsVector, class _ExecutionPolicy, class _RandomAccessIterator, class _Tp, class _BinaryOperation,
130           class _UnaryOperation>
131 _Tp
__pattern_transform_reduce(__parallel_tag<_IsVector> __tag,_ExecutionPolicy && __exec,_RandomAccessIterator __first,_RandomAccessIterator __last,_Tp __init,_BinaryOperation __binary_op,_UnaryOperation __unary_op)132 __pattern_transform_reduce(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _RandomAccessIterator __first,
133                            _RandomAccessIterator __last, _Tp __init, _BinaryOperation __binary_op,
134                            _UnaryOperation __unary_op)
135 {
136     using __backend_tag = typename decltype(__tag)::__backend_tag;
137 
138     return __internal::__except_handler(
139         [&]()
140         {
141             return __par_backend::__parallel_transform_reduce(
142                 __backend_tag{}, std::forward<_ExecutionPolicy>(__exec), __first, __last,
143                 [__unary_op](_RandomAccessIterator __i) mutable { return __unary_op(*__i); }, __init, __binary_op,
144                 [__unary_op, __binary_op](_RandomAccessIterator __i, _RandomAccessIterator __j, _Tp __init) {
145                     return __internal::__brick_transform_reduce(__i, __j, __init, __binary_op, __unary_op, _IsVector{});
146                 });
147         });
148 }
149 
150 //------------------------------------------------------------------------
151 // transform_exclusive_scan
152 //
153 // walk3 evaluates f(x,y,z) for (x,y,z) drawn from [first1,last1), [first2,...), [first3,...)
154 //------------------------------------------------------------------------
155 
156 // Exclusive form
157 template <class _ForwardIterator, class _OutputIterator, class _UnaryOperation, class _Tp, class _BinaryOperation>
158 std::pair<_OutputIterator, _Tp>
__brick_transform_scan(_ForwardIterator __first,_ForwardIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,std::false_type,std::false_type)159 __brick_transform_scan(_ForwardIterator __first, _ForwardIterator __last, _OutputIterator __result,
160                        _UnaryOperation __unary_op, _Tp __init, _BinaryOperation __binary_op,
161                        /*Inclusive*/ std::false_type, /*is_vector=*/std::false_type) noexcept
162 {
163     for (; __first != __last; ++__first, ++__result)
164     {
165         *__result = __init;
166         _PSTL_PRAGMA_FORCEINLINE
167         __init = __binary_op(__init, __unary_op(*__first));
168     }
169     return std::make_pair(__result, __init);
170 }
171 
172 // Inclusive form
173 template <class _RandomAccessIterator, class _OutputIterator, class _UnaryOperation, class _Tp, class _BinaryOperation>
174 std::pair<_OutputIterator, _Tp>
__brick_transform_scan(_RandomAccessIterator __first,_RandomAccessIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,std::true_type,std::false_type)175 __brick_transform_scan(_RandomAccessIterator __first, _RandomAccessIterator __last, _OutputIterator __result,
176                        _UnaryOperation __unary_op, _Tp __init, _BinaryOperation __binary_op,
177                        /*Inclusive*/ std::true_type, /*is_vector=*/std::false_type) noexcept
178 {
179     for (; __first != __last; ++__first, ++__result)
180     {
181         _PSTL_PRAGMA_FORCEINLINE
182         __init = __binary_op(__init, __unary_op(*__first));
183         *__result = __init;
184     }
185     return std::make_pair(__result, __init);
186 }
187 
188 // type is arithmetic and binary operation is a user defined operation.
189 template <typename _Tp, typename _BinaryOperation>
190 using is_arithmetic_udop = std::integral_constant<bool, std::is_arithmetic<_Tp>::value &&
191                                                             !std::is_same<_BinaryOperation, std::plus<_Tp>>::value>;
192 
193 // [restriction] - T shall be DefaultConstructible.
194 // [violation] - default ctor of T shall set the identity value for binary_op.
195 template <class _RandomAccessIterator, class _OutputIterator, class _UnaryOperation, class _Tp, class _BinaryOperation,
196           class _Inclusive>
197 typename std::enable_if<!is_arithmetic_udop<_Tp, _BinaryOperation>::value, std::pair<_OutputIterator, _Tp>>::type
__brick_transform_scan(_RandomAccessIterator __first,_RandomAccessIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,_Inclusive,std::true_type)198 __brick_transform_scan(_RandomAccessIterator __first, _RandomAccessIterator __last, _OutputIterator __result,
199                        _UnaryOperation __unary_op, _Tp __init, _BinaryOperation __binary_op, _Inclusive,
200                        /*is_vector=*/std::true_type) noexcept
201 {
202 #if defined(_PSTL_UDS_PRESENT)
203     return __unseq_backend::__simd_scan(__first, __last - __first, __result, __unary_op, __init, __binary_op,
204                                         _Inclusive());
205 #else
206     // We need to call serial brick here to call function for inclusive and exclusive scan that depends on _Inclusive() value
207     return __internal::__brick_transform_scan(__first, __last, __result, __unary_op, __init, __binary_op, _Inclusive(),
208                                               /*is_vector=*/std::false_type());
209 #endif
210 }
211 
212 template <class _RandomAccessIterator, class _OutputIterator, class _UnaryOperation, class _Tp, class _BinaryOperation,
213           class _Inclusive>
214 typename std::enable_if<is_arithmetic_udop<_Tp, _BinaryOperation>::value, std::pair<_OutputIterator, _Tp>>::type
__brick_transform_scan(_RandomAccessIterator __first,_RandomAccessIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,_Inclusive,std::true_type)215 __brick_transform_scan(_RandomAccessIterator __first, _RandomAccessIterator __last, _OutputIterator __result,
216                        _UnaryOperation __unary_op, _Tp __init, _BinaryOperation __binary_op, _Inclusive,
217                        /*is_vector=*/std::true_type) noexcept
218 {
219     return __internal::__brick_transform_scan(__first, __last, __result, __unary_op, __init, __binary_op, _Inclusive(),
220                                               /*is_vector=*/std::false_type());
221 }
222 
223 template <class _Tag, class _ExecutionPolicy, class _ForwardIterator, class _OutputIterator, class _UnaryOperation,
224           class _Tp, class _BinaryOperation, class _Inclusive>
225 _OutputIterator
__pattern_transform_scan(_Tag,_ExecutionPolicy &&,_ForwardIterator __first,_ForwardIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,_Inclusive)226 __pattern_transform_scan(_Tag, _ExecutionPolicy&&, _ForwardIterator __first, _ForwardIterator __last,
227                          _OutputIterator __result, _UnaryOperation __unary_op, _Tp __init, _BinaryOperation __binary_op,
228                          _Inclusive) noexcept
229 {
230     return __internal::__brick_transform_scan(__first, __last, __result, __unary_op, __init, __binary_op, _Inclusive(),
231                                               typename _Tag::__is_vector{})
232         .first;
233 }
234 
235 template <class _IsVector, class _ExecutionPolicy, class _RandomAccessIterator, class _OutputIterator,
236           class _UnaryOperation, class _Tp, class _BinaryOperation, class _Inclusive>
237 typename std::enable_if<!std::is_floating_point<_Tp>::value, _OutputIterator>::type
__pattern_transform_scan(__parallel_tag<_IsVector> __tag,_ExecutionPolicy && __exec,_RandomAccessIterator __first,_RandomAccessIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,_Inclusive)238 __pattern_transform_scan(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _RandomAccessIterator __first,
239                          _RandomAccessIterator __last, _OutputIterator __result, _UnaryOperation __unary_op, _Tp __init,
240                          _BinaryOperation __binary_op, _Inclusive)
241 {
242     using __backend_tag = typename decltype(__tag)::__backend_tag;
243 
244     typedef typename std::iterator_traits<_RandomAccessIterator>::difference_type _DifferenceType;
245 
246     return __internal::__except_handler(
247         [&]()
248         {
249             __par_backend::__parallel_transform_scan(
250                 __backend_tag{}, std::forward<_ExecutionPolicy>(__exec), __last - __first,
251                 [__first, __unary_op](_DifferenceType __i) mutable { return __unary_op(__first[__i]); }, __init,
252                 __binary_op,
253                 [__first, __unary_op, __binary_op](_DifferenceType __i, _DifferenceType __j, _Tp __init)
254                 {
255                     // Execute serial __brick_transform_reduce, due to the explicit SIMD vectorization (reduction) requires a commutative operation for the guarantee of correct scan.
256                     return __internal::__brick_transform_reduce(__first + __i, __first + __j, __init, __binary_op,
257                                                                 __unary_op,
258                                                                 /*__is_vector*/ std::false_type());
259                 },
260                 [__first, __unary_op, __binary_op, __result](_DifferenceType __i, _DifferenceType __j, _Tp __init)
261                 {
262                     return __internal::__brick_transform_scan(__first + __i, __first + __j, __result + __i, __unary_op,
263                                                               __init, __binary_op, _Inclusive(), _IsVector{})
264                         .second;
265                 });
266             return __result + (__last - __first);
267         });
268 }
269 
270 template <class _IsVector, class _ExecutionPolicy, class _RandomAccessIterator, class _OutputIterator,
271           class _UnaryOperation, class _Tp, class _BinaryOperation, class _Inclusive>
272 typename std::enable_if<std::is_floating_point<_Tp>::value, _OutputIterator>::type
__pattern_transform_scan(__parallel_tag<_IsVector> __tag,_ExecutionPolicy && __exec,_RandomAccessIterator __first,_RandomAccessIterator __last,_OutputIterator __result,_UnaryOperation __unary_op,_Tp __init,_BinaryOperation __binary_op,_Inclusive)273 __pattern_transform_scan(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec, _RandomAccessIterator __first,
274                          _RandomAccessIterator __last, _OutputIterator __result, _UnaryOperation __unary_op, _Tp __init,
275                          _BinaryOperation __binary_op, _Inclusive)
276 {
277     using __backend_tag = typename decltype(__tag)::__backend_tag;
278 
279     typedef typename std::iterator_traits<_RandomAccessIterator>::difference_type _DifferenceType;
280     _DifferenceType __n = __last - __first;
281 
282     if (__n <= 0)
283     {
284         return __result;
285     }
286     return __internal::__except_handler(
287         [&]()
288         {
289             __par_backend::__parallel_strict_scan(
290                 __backend_tag{}, std::forward<_ExecutionPolicy>(__exec), __n, __init,
291                 [__first, __unary_op, __binary_op, __result](_DifferenceType __i, _DifferenceType __len)
292                 {
293                     return __internal::__brick_transform_scan(__first + __i, __first + (__i + __len), __result + __i,
294                                                               __unary_op, _Tp{}, __binary_op, _Inclusive(), _IsVector{})
295                         .second;
296                 },
297                 __binary_op,
298                 [__result, &__binary_op](_DifferenceType __i, _DifferenceType __len, _Tp __initial)
299                 {
300                     return *(std::transform(__result + __i, __result + __i + __len, __result + __i,
301                                             [&__initial, &__binary_op](const _Tp& __x)
302                                             {
303                                                 _PSTL_PRAGMA_FORCEINLINE
304                                                 return __binary_op(__initial, __x);
305                                             }) -
306                              1);
307                 },
308                 [](_Tp) {});
309             return __result + (__last - __first);
310         });
311 }
312 
313 //------------------------------------------------------------------------
314 // adjacent_difference
315 //------------------------------------------------------------------------
316 
317 template <class _ForwardIterator, class _OutputIterator, class _BinaryOperation>
318 _OutputIterator
__brick_adjacent_difference(_ForwardIterator __first,_ForwardIterator __last,_OutputIterator __d_first,_BinaryOperation __op,std::false_type)319 __brick_adjacent_difference(_ForwardIterator __first, _ForwardIterator __last, _OutputIterator __d_first,
320                             _BinaryOperation __op, /*is_vector*/ std::false_type) noexcept
321 {
322     return std::adjacent_difference(__first, __last, __d_first, __op);
323 }
324 
325 template <class _RandomAccessIterator1, class _RandomAccessIterator2, class BinaryOperation>
326 _RandomAccessIterator2
__brick_adjacent_difference(_RandomAccessIterator1 __first,_RandomAccessIterator1 __last,_RandomAccessIterator2 __d_first,BinaryOperation __op,std::true_type)327 __brick_adjacent_difference(_RandomAccessIterator1 __first, _RandomAccessIterator1 __last,
328                             _RandomAccessIterator2 __d_first, BinaryOperation __op,
329                             /*is_vector=*/std::true_type) noexcept
330 {
331     _PSTL_ASSERT(__first != __last);
332 
333     typedef typename std::iterator_traits<_RandomAccessIterator1>::reference _ReferenceType1;
334     typedef typename std::iterator_traits<_RandomAccessIterator2>::reference _ReferenceType2;
335 
336     auto __n = __last - __first;
337     *__d_first = *__first;
338     return __unseq_backend::__simd_walk_3(
339         __first + 1, __n - 1, __first, __d_first + 1,
340         [&__op](_ReferenceType1 __x, _ReferenceType1 __y, _ReferenceType2 __z) { __z = __op(__x, __y); });
341 }
342 
343 template <class _Tag, class _ExecutionPolicy, class _ForwardIterator, class _OutputIterator, class _BinaryOperation>
344 _OutputIterator
__pattern_adjacent_difference(_Tag,_ExecutionPolicy &&,_ForwardIterator __first,_ForwardIterator __last,_OutputIterator __d_first,_BinaryOperation __op)345 __pattern_adjacent_difference(_Tag, _ExecutionPolicy&&, _ForwardIterator __first, _ForwardIterator __last,
346                               _OutputIterator __d_first, _BinaryOperation __op) noexcept
347 {
348     return __internal::__brick_adjacent_difference(__first, __last, __d_first, __op, typename _Tag::__is_vector{});
349 }
350 
351 template <class _IsVector, class _ExecutionPolicy, class _RandomAccessIterator1, class _RandomAccessIterator2,
352           class _BinaryOperation>
353 _RandomAccessIterator2
__pattern_adjacent_difference(__parallel_tag<_IsVector> __tag,_ExecutionPolicy && __exec,_RandomAccessIterator1 __first,_RandomAccessIterator1 __last,_RandomAccessIterator2 __d_first,_BinaryOperation __op)354 __pattern_adjacent_difference(__parallel_tag<_IsVector> __tag, _ExecutionPolicy&& __exec,
355                               _RandomAccessIterator1 __first, _RandomAccessIterator1 __last,
356                               _RandomAccessIterator2 __d_first, _BinaryOperation __op)
357 {
358     _PSTL_ASSERT(__first != __last);
359     typedef typename std::iterator_traits<_RandomAccessIterator1>::reference _ReferenceType1;
360     typedef typename std::iterator_traits<_RandomAccessIterator2>::reference _ReferenceType2;
361 
362     using __backend_tag = typename decltype(__tag)::__backend_tag;
363 
364     *__d_first = *__first;
365     __par_backend::__parallel_for(__backend_tag{}, std::forward<_ExecutionPolicy>(__exec), __first, __last - 1,
366                                   [&__op, __d_first, __first](_RandomAccessIterator1 __b, _RandomAccessIterator1 __e)
367                                   {
368                                       _RandomAccessIterator2 __d_b = __d_first + (__b - __first);
369                                       __internal::__brick_walk3(
370                                           __b, __e, __b + 1, __d_b + 1,
371                                           [&__op](_ReferenceType1 __x, _ReferenceType1 __y, _ReferenceType2 __z)
372                                           { __z = __op(__y, __x); },
373                                           _IsVector{});
374                                   });
375     return __d_first + (__last - __first);
376 }
377 
378 } // namespace __internal
379 } // namespace __pstl
380 
381 _PSTL_HIDE_FROM_ABI_POP
382 
383 #endif /* _PSTL_NUMERIC_IMPL_H */
384