diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 5f7afec336c..893042036f1 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -122,6 +122,7 @@ add_benchmark(nth_element src/nth_element.cpp) add_benchmark(path_lexically_normal src/path_lexically_normal.cpp) add_benchmark(priority_queue_push_range src/priority_queue_push_range.cpp) add_benchmark(random_integer_generation src/random_integer_generation.cpp) +add_benchmark(ranges_div_ceil src/ranges_div_ceil.cpp) add_benchmark(regex_search src/regex_search.cpp) add_benchmark(remove src/remove.cpp) add_benchmark(replace src/replace.cpp) diff --git a/benchmarks/src/ranges_div_ceil.cpp b/benchmarks/src/ranges_div_ceil.cpp new file mode 100644 index 00000000000..2d6a927e8c1 --- /dev/null +++ b/benchmarks/src/ranges_div_ceil.cpp @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +using namespace std; + +struct Data { + _Signed128 num; + _Signed128 den; +}; + +constexpr Data d1{_Signed128{0x0000'1111'2222'3333LL, 0x4444'5555'6666'7777LL}, 0x7777'8888}; +static_assert(d1.num > numeric_limits::max()); +static_assert(d1.den <= numeric_limits::max()); + +constexpr Data d2{_Signed128{0x0000'1111'2222'3333LL, 0x4444'5555'6666'7777LL}, 0x7777'8888'9999'AAAA}; +static_assert(d2.num > numeric_limits::max()); +static_assert(d2.den <= numeric_limits::max()); + +constexpr Data d3{ + _Signed128{0x0000'1111'2222'3333LL, 0x4444'5555'6666'7777LL}, _Signed128{0x7777'8888'9999'AAAA, 0xBBBB}}; +static_assert(d3.num > numeric_limits::max()); +static_assert(d3.den > numeric_limits::max()); + +void bm(benchmark::State& state, const Data& data) { + for (auto _ : state) { + benchmark::DoNotOptimize(ranges::_Div_ceil(data.num, data.den)); + } +} + +BENCHMARK_CAPTURE(bm, div_ceil_int128_uint32, d1); +BENCHMARK_CAPTURE(bm, div_ceil_int128_uint64, d2); +BENCHMARK_CAPTURE(bm, div_ceil_int128_int128, d3); + +BENCHMARK_MAIN(); diff --git a/stl/inc/__msvc_int128.hpp b/stl/inc/__msvc_int128.hpp index 8276f66ddd9..34f9bc99003 100644 --- a/stl/inc/__msvc_int128.hpp +++ b/stl/inc/__msvc_int128.hpp @@ -435,8 +435,8 @@ struct alignas(16) _Base128 { _NODISCARD static constexpr _Base128 _Divide(const _Base128& _Num, const uint64_t _Den) noexcept { _Base128 _Result; _Result._Word[1] = _Num._Word[1] / _Den; - uint64_t _Rem = _Num._Word[1] % _Den; - _Result._Word[0] = _UDiv128(_Rem, _Num._Word[0], _Den, _Rem); + uint64_t _Rem _ZERO_OR_NO_INIT; + _Result._Word[0] = _UDiv128(_Num._Word[1] % _Den, _Num._Word[0], _Den, _Rem); return _Result; } _NODISCARD static constexpr _Base128 _Divide(_Base128 _Num, _Base128 _Den) noexcept { @@ -678,6 +678,168 @@ struct alignas(16) _Base128 { return _Num; } +#if _HAS_CXX23 +#if !_STL_128_DIV_INTRINSICS + _NODISCARD static constexpr _Base128 _Div_ceil(const _Base128& _Num, const uint32_t _Den) noexcept { + _Base128 _Result; + _Result._Word[1] = _Num._Word[1] / _Den; + uint64_t _Rem = ((_Num._Word[1] % _Den) << 32) | (_Num._Word[0] >> 32); + _Result._Word[0] = (_Rem / _Den) << 32; + _Rem = ((_Rem % _Den) << 32) | static_cast(_Num._Word[0]); + _Result._Word[0] |= static_cast(_Rem / _Den); + if (_Rem % _Den != 0) { + ++_Result; + } + + return _Result; + } +#endif // !_STL_128_DIV_INTRINSICS + + _NODISCARD static constexpr _Base128 _Div_ceil(const _Base128& _Num, const uint64_t _Den) noexcept { + _Base128 _Result; + _Result._Word[1] = _Num._Word[1] / _Den; + uint64_t _Rem; + _Result._Word[0] = _UDiv128(_Num._Word[1] % _Den, _Num._Word[0], _Den, _Rem); + if (_Rem != 0) { + ++_Result; + } + + return _Result; + } + + _NODISCARD static constexpr _Base128 _Div_ceil(_Base128 _Num, _Base128 _Den) noexcept { + // establish _Den < _Num and _Num._Word[1] > 0 + if (_Den._Word[1] >= _Num._Word[1]) { + if (_Den._Word[1] > _Num._Word[1]) { + return static_cast<_Base128>(_Num != 0); + } + + if (_Num._Word[1] == 0) { + uint64_t _Result = _Num._Word[0] / _Den._Word[0]; // with 64-bit inputs, the ceiling is also 64-bit + if (_Num._Word[0] % _Den._Word[0] != 0) { + ++_Result; + } + + return _Result; + } + + if (_Num._Word[0] > _Den._Word[0]) { + return 2u; + } + + return 1u; + } + + // establish _Den has more than 1 non-zero "digit" + if (_Den._Word[1] == 0) { +#if !_STL_128_DIV_INTRINSICS + if (_Den._Word[0] < (1ull << 32)) { + return _Div_ceil(_Num, static_cast(_Den._Word[0])); + } else +#endif // !_STL_128_DIV_INTRINSICS + { + return _Div_ceil(_Num, _Den._Word[0]); + } + } + + _Base128 _Result; +#if _STL_128_INTRINSICS + // Knuth 4.3.1D, 2-digit by 2-digit divide in base 2^64 + // _STL_INTERNAL_CHECK(_Den._Word[1] != 0); + // _STL_INTERNAL_CHECK(_Num._Word[1] > _Den._Word[1]); + // Normalize by shifting both left until _Den's high bit is set (So _Den's high digit is >= b / 2) + const auto __d = _STD _Countl_zero_internal(_Den._Word[1]); + _Den <<= __d; + auto _High_digit = __d == 0 ? 0 : _Num._Word[1] >> (64 - __d); // This creates a third digit for _Num + _Num <<= __d; + + _Base128 __qhat; + __qhat._Word[1] = _High_digit >= _Den._Word[1]; + uint64_t __rhat; + __qhat._Word[0] = _UDiv128(_High_digit >= _Den._Word[1] ? _High_digit - _Den._Word[1] : _High_digit, + _Num._Word[1], _Den._Word[1], __rhat); + + for (;;) { + if (__qhat._Word[1] > 0) { + --__qhat; + } else { + _Base128 _Prod; + _Prod._Word[0] = _UMul128(__qhat._Word[0], _Den._Word[0], _Prod._Word[1]); + if (_Prod <= _Base128{_Num._Word[0], __rhat}) { + break; + } + --__qhat._Word[0]; + } + + const auto _Sum = __rhat + _Den._Word[1]; + if (__rhat > _Sum) { + break; + } + __rhat = _Sum; + } + // _STL_INTERNAL_CHECK(__qhat._Word[1] == 0); + + // [_High_digit | _Num] -= __qhat * _Den [Since __qhat < b, this is 3-digit - 1-digit * 2-digit] + uint64_t _Prod0_hi; + uint64_t _Prod_lo = _UMul128(__qhat._Word[0], _Den._Word[0], _Prod0_hi); + auto _Borrow = _SubBorrow64(0, _Num._Word[0], _Prod_lo, _Num._Word[0]); + uint64_t _Prod1_hi; + _Prod_lo = _UMul128(__qhat._Word[0], _Den._Word[1], _Prod1_hi); + _Prod1_hi += _AddCarry64(0, _Prod_lo, _Prod0_hi, _Prod_lo); + _Borrow = _SubBorrow64(_Borrow, _Num._Word[1], _Prod_lo, _Num._Word[1]); + _Borrow = _SubBorrow64(_Borrow, _High_digit, _Prod1_hi, _High_digit); + if (_Borrow) { + --__qhat._Word[0]; + auto _Carry = _AddCarry64(0, _Num._Word[0], _Den._Word[0], _Num._Word[0]); + (void) _AddCarry64(_Carry, _Num._Word[1], _Den._Word[1], _Num._Word[1]); + } + _Result = __qhat; +#else // ^^^ 128-bit intrinsics / no such intrinsics vvv + auto __d = _Countl_zero_internal(_Den._Word[1]); + const bool _Three_word_den = __d >= 32; + __d &= 31; + uint32_t __u[5]{ + static_cast(_Num._Word[0] << __d), + static_cast(_Num._Word[0] >> (32 - __d)), + static_cast(_Num._Word[1] << __d), + static_cast(_Num._Word[1] >> (32 - __d)), + 0, + }; + uint32_t __v[4] = { + static_cast(_Den._Word[0] << __d), + static_cast(_Den._Word[0] >> (32 - __d)), + static_cast(_Den._Word[1] << __d), + static_cast(_Den._Word[1] >> (32 - __d)), + }; + if (__d != 0) { + __u[2] |= _Num._Word[0] >> (64 - __d); + __u[4] |= _Num._Word[1] >> (64 - __d); + __v[2] |= _Den._Word[0] >> (64 - __d); + } + + uint32_t __q[2] = {}; + if (_Three_word_den) { + // 4-digit by 3-digit base 2^32 division + _Knuth_4_3_1_D(__u, 5, __v, 3, __q); + // _STL_INTERNAL_CHECK(__u[3] == 0); + } else { + // 4-digit by 4-digit base 2^32 division + _Knuth_4_3_1_D(__u, 5, __v, 4, __q); + } + + _Result = (static_cast(__q[1]) << 32) | __q[0]; + _Num._Word[0] = (static_cast(__u[1]) << 32) | __u[0]; + _Num._Word[1] = (static_cast(__u[3]) << 32) | __u[2]; +#endif // _STL_128_INTRINSICS + + if (_Num != 0) { + ++_Result; + } + + return _Result; + } +#endif // _HAS_CXX23 + _TEMPLATE_CLASS_INTEGRAL(_Ty) friend constexpr _Ty& operator&=(_Ty& _Left, const _Base128& _Right) noexcept { _Left &= _Right._Word[0]; @@ -1327,6 +1489,14 @@ struct _Signed128 : _Base128 { return _Left; } +#if _HAS_CXX23 + _NODISCARD static constexpr _Signed128 _Div_ceil(_Signed128 _Num, _Signed128 _Den) noexcept { + // _STL_INTERNAL_CHECK(_Num >= 0); + // _STL_INTERNAL_CHECK(_Den > 0); + return _Signed128{_Base128::_Div_ceil(_Num, _Den)}; + } +#endif // _HAS_CXX23 + _NODISCARD friend constexpr _Signed128 operator&(const _Signed128& _Left, const _Signed128& _Right) noexcept { return _Signed128{_Left._Word[0] & _Right._Word[0], _Left._Word[1] & _Right._Word[1]}; } diff --git a/stl/inc/ranges b/stl/inc/ranges index 0033e663051..1374e0bce35 100644 --- a/stl/inc/ranges +++ b/stl/inc/ranges @@ -5470,13 +5470,21 @@ namespace ranges { _EXPORT_STD inline constexpr _Enumerate_fn enumerate; } // namespace views - template - _NODISCARD constexpr _Size _Div_ceil(const _Size _Num, const _Size _Denom) noexcept { - _Size _Result = _Num / _Denom; - if (_Num % _Denom != 0) { - ++_Result; + template + _NODISCARD constexpr _Int _Div_ceil(const _Int _Num, const _Int _Denom) noexcept { + _STL_INTERNAL_STATIC_ASSERT(_Signed_integer_like<_Int>); + _STL_INTERNAL_CHECK(_Num >= 0); + _STL_INTERNAL_CHECK(_Denom > 0); + + if constexpr (same_as<_Int, _Signed128>) { + return _Int::_Div_ceil(_Num, _Denom); + } else { + _Int _Result = _Num / _Denom; + if (_Num % _Denom != 0) { + ++_Result; + } + return _Result; } - return _Result; } _EXPORT_STD template diff --git a/tests/std/test.lst b/tests/std/test.lst index 8b9164be588..247308b4cbc 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -684,6 +684,7 @@ tests\P2440R1_ranges_alg_shift_left tests\P2440R1_ranges_alg_shift_right tests\P2440R1_ranges_numeric_iota tests\P2441R2_views_join_with +tests\P2442R1_signed128_div_ceil tests\P2442R1_views_chunk tests\P2442R1_views_chunk_death tests\P2442R1_views_slide diff --git a/tests/std/tests/P2442R1_signed128_div_ceil/env.lst b/tests/std/tests/P2442R1_signed128_div_ceil/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/P2442R1_signed128_div_ceil/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/P2442R1_signed128_div_ceil/test.cpp b/tests/std/tests/P2442R1_signed128_div_ceil/test.cpp new file mode 100644 index 00000000000..d73cbfb1bc5 --- /dev/null +++ b/tests/std/tests/P2442R1_signed128_div_ceil/test.cpp @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include <__msvc_int128.hpp> +#include +#include + +constexpr void test() { + using std::_Signed128; + + assert(_Signed128::_Div_ceil(0, 1) == 0); + assert(_Signed128::_Div_ceil(1, 1) == 1); + assert(_Signed128::_Div_ceil(2, 1) == 2); + + assert(_Signed128::_Div_ceil(116, 3) == 39); + assert(_Signed128::_Div_ceil(205, 24) == 9); + assert(_Signed128::_Div_ceil(83, 1) == 83); + assert(_Signed128::_Div_ceil(240, 13) == 19); + assert(_Signed128::_Div_ceil(100, 13) == 8); + + assert(_Signed128::_Div_ceil(149, 147) == 2); + assert(_Signed128::_Div_ceil(122, 152) == 1); + assert(_Signed128::_Div_ceil(154, 236) == 1); + assert(_Signed128::_Div_ceil(163, 251) == 1); + assert(_Signed128::_Div_ceil(89, 199) == 1); + + assert(_Signed128::_Div_ceil(1069796746, 23678) == 45182); + assert(_Signed128::_Div_ceil(1200533627, 8126) == 147740); + assert(_Signed128::_Div_ceil(1483929008, 31399) == 47261); + assert(_Signed128::_Div_ceil(40124559, 11161) == 3596); + assert(_Signed128::_Div_ceil(616936466, 18476) == 33392); + + assert(_Signed128::_Div_ceil(3823598863U, 26233) == 145756); + assert(_Signed128::_Div_ceil(1748736303, 60486) == 28912); + assert(_Signed128::_Div_ceil(3727652564U, 11487) == 324511); + assert(_Signed128::_Div_ceil(443086302, 51740) == 8564); + assert(_Signed128::_Div_ceil(2309966593U, 45205) == 51100); + + assert(_Signed128::_Div_ceil(1688186001, 2331624069U) == 1); + assert(_Signed128::_Div_ceil(4072558058U, 202653587) == 21); + assert(_Signed128::_Div_ceil(551570633, 2681152300U) == 1); + assert(_Signed128::_Div_ceil(3166723164U, 852231237) == 4); + assert(_Signed128::_Div_ceil(3238568162U, 1640506845) == 2); + + assert(_Signed128::_Div_ceil(6242708709713706173ULL, 986240334) == 6329804709ULL); + assert(_Signed128::_Div_ceil(5971429753141872861ULL, 773448951) == 7720522144ULL); + assert(_Signed128::_Div_ceil(3773313860741815071ULL, 2965323624U) == 1272479615); + assert(_Signed128::_Div_ceil(5545364762704150466ULL, 264975532) == 20927837076ULL); + assert(_Signed128::_Div_ceil(3730152929068983090ULL, 2492151938U) == 1496759838); + + assert(_Signed128::_Div_ceil(10191699552117669113ULL, 3524412433243714339ULL) == 3); + assert(_Signed128::_Div_ceil(3073175754216384423ULL, 8155635103385767810ULL) == 1); + assert(_Signed128::_Div_ceil(14454347299931791225ULL, 1125386058911220070ULL) == 13); + assert(_Signed128::_Div_ceil(13659816168029181560ULL, 5789718827007400904ULL) == 3); + assert(_Signed128::_Div_ceil(1069287642992917145ULL, 738486755903428333ULL) == 2); + + assert((_Signed128::_Div_ceil(_Signed128{1329496395917005583ULL, 4825206483521586190ULL}, 2328480880092472314ULL) + == _Signed128{1332872165572535026ULL, 2})); + assert(_Signed128::_Div_ceil(_Signed128{9730685006318077192ULL, 7232491004677529961ULL}, 7551609526229071216ULL) + == 17667215196349628672ULL); + assert((_Signed128::_Div_ceil(_Signed128{12172154442470927259ULL, 8777428261169746305ULL}, 8637741546317560676ULL) + == _Signed128{298314676997257110ULL, 1})); + assert((_Signed128::_Div_ceil(_Signed128{15481671640377153953ULL, 8903114101091695213ULL}, 6090891903637929972ULL) + == _Signed128{8517035596026637315ULL, 1})); + assert(_Signed128::_Div_ceil(_Signed128{15743145484066333195ULL, 805665491188157799ULL}, 6585636204170695090ULL) + == 2256715169835129064ULL); + + assert(_Signed128::_Div_ceil(_Signed128{10731879507814251049ULL, 3873445590U}, + _Signed128{12450032536891019435ULL, 9105649371241646359ULL}) + == 1); + assert(_Signed128::_Div_ceil(_Signed128{6629798443635676577ULL, 2327383978U}, + _Signed128{9839716022772647304ULL, 7922581345850742364ULL}) + == 1); + assert(_Signed128::_Div_ceil(_Signed128{11339831796615584288ULL, 4257443653U}, + _Signed128{4227111794755706639ULL, 670374651106134374ULL}) + == 1); + assert(_Signed128::_Div_ceil(_Signed128{8670574441769227246ULL, 2507472313U}, + _Signed128{6980882583348139841ULL, 4304389704649692956ULL}) + == 1); + assert(_Signed128::_Div_ceil(0, _Signed128{6980882583348139841ULL, 4304389704649692956ULL}) == 0); + + assert(_Signed128::_Div_ceil(_Signed128{6864055997479949219ULL, 8622452334377655874ULL}, + _Signed128{2561354846261717143ULL, 1392383021258532795ULL}) + == 7); + assert(_Signed128::_Div_ceil(_Signed128{939441510092257833ULL, 520549398019160129ULL}, + _Signed128{2009633185566611151ULL, 8480432536590844791ULL}) + == 1); + assert(_Signed128::_Div_ceil(_Signed128{10105653851413328809ULL, 6012530092550053736ULL}, + _Signed128{5123113788439280060ULL, 6703818145194006067ULL}) + == 1); + assert(_Signed128::_Div_ceil(_Signed128{10851415781288494064ULL, 6183428757366606812ULL}, + _Signed128{2541106769657089592ULL, 2041369374582624895ULL}) + == 4); + assert(_Signed128::_Div_ceil(_Signed128{3797056150347910189ULL, 6463249530114157684ULL}, + _Signed128{12268041577665946814ULL, 7107698248126780543ULL}) + == 1); + + constexpr _Signed128 mx = std::numeric_limits<_Signed128>::max(); + assert(_Signed128::_Div_ceil(mx, mx) == 1); + assert(_Signed128::_Div_ceil(mx - 1, mx) == 1); + assert(_Signed128::_Div_ceil(mx, mx - 1) == 2); + assert(_Signed128::_Div_ceil(mx, 1) == mx); + assert(_Signed128::_Div_ceil(mx, 2) == mx / 2 + 1); +} + +int main() { + static_assert((test(), true)); + test(); +}