diff --git a/.github/actions/spell-check/dictionary/math.txt b/.github/actions/spell-check/dictionary/math.txt index a533e3d12f..4bad249611 100644 --- a/.github/actions/spell-check/dictionary/math.txt +++ b/.github/actions/spell-check/dictionary/math.txt @@ -1,2 +1,3 @@ powf sqrtf +isnan diff --git a/.github/actions/spell-check/whitelist/whitelist.txt b/.github/actions/spell-check/whitelist/whitelist.txt index b7ceec203a..6f102a7b72 100644 --- a/.github/actions/spell-check/whitelist/whitelist.txt +++ b/.github/actions/spell-check/whitelist/whitelist.txt @@ -482,6 +482,7 @@ cwchar cwctype cwd cx +cxcy CXFRAME CXFULLSCREEN CXHSCROLL diff --git a/src/inc/til.h b/src/inc/til.h index 5204284a00..e83d2cffe4 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -7,6 +7,7 @@ #include "til/at.h" #include "til/color.h" +#include "til/math.h" #include "til/some.h" #include "til/size.h" #include "til/point.h" diff --git a/src/inc/til/math.h b/src/inc/til/math.h new file mode 100644 index 0000000000..92afa100c0 --- /dev/null +++ b/src/inc/til/math.h @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +namespace til +{ + // The til::math namespace contains TIL math guidance casts; + // they are intended to be used as the first argument to + // floating-point universal converters elsewhere in the til namespace. + namespace math + { + namespace details + { + struct ceiling_t + { + template + static O cast(T val) + { + if constexpr (std::is_floating_point_v) + { + THROW_HR_IF(E_ABORT, ::std::isnan(val)); + return ::base::saturated_cast(::std::ceil(val)); + } + else + { + return ::base::saturated_cast(val); + } + } + }; + + struct flooring_t + { + template + static O cast(T val) + { + if constexpr (std::is_floating_point_v) + { + THROW_HR_IF(E_ABORT, ::std::isnan(val)); + return ::base::saturated_cast(::std::floor(val)); + } + else + { + return ::base::saturated_cast(val); + } + } + }; + + struct rounding_t + { + template + static O cast(T val) + { + if constexpr (std::is_floating_point_v) + { + THROW_HR_IF(E_ABORT, ::std::isnan(val)); + return ::base::saturated_cast(::std::round(val)); + } + else + { + return ::base::saturated_cast(val); + } + } + }; + + struct truncating_t + { + template + static O cast(T val) + { + if constexpr (std::is_floating_point_v) + { + THROW_HR_IF(E_ABORT, ::std::isnan(val)); + } + return ::base::saturated_cast(val); + } + }; + } + + static constexpr details::ceiling_t ceiling; // positives become more positive, negatives become less negative + static constexpr details::flooring_t flooring; // positives become less positive, negatives become more negative + static constexpr details::rounding_t rounding; // it's rounding, from math class + static constexpr details::truncating_t truncating; // drop the decimal point, regardless of how close it is to the next value + } +} diff --git a/src/inc/til/point.h b/src/inc/til/point.h index 39a465f073..81691d7b64 100644 --- a/src/inc/til/point.h +++ b/src/inc/til/point.h @@ -53,6 +53,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } + // This template will convert to size from anything that has a X and a Y field that are floating-point; + // a math type is required. + template + constexpr point(TilMath, const TOther& other, std::enable_if_t().X)> && std::is_floating_point_v().Y)>, int> /*sentinel*/ = 0) : + point(TilMath::template cast(other.X), TilMath::template cast(other.Y)) + { + } + + // This template will convert to size from anything that has a x and a y field that are floating-point; + // a math type is required. + template + constexpr point(TilMath, const TOther& other, std::enable_if_t().x)> && std::is_floating_point_v().y)>, int> /*sentinel*/ = 0) : + point(TilMath::template cast(other.x), TilMath::template cast(other.y)) + { + } + constexpr bool operator==(const point& other) const noexcept { return _x == other._x && diff --git a/src/inc/til/size.h b/src/inc/til/size.h index e551980fbd..67a4102382 100644 --- a/src/inc/til/size.h +++ b/src/inc/til/size.h @@ -53,6 +53,30 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } + // This template will convert to size from anything that has a X and a Y field that are floating-point; + // a math type is required. + template + constexpr size(TilMath, const TOther& other, std::enable_if_t().X)> && std::is_floating_point_v().Y)>, int> /*sentinel*/ = 0) : + size(TilMath::template cast(other.X), TilMath::template cast(other.Y)) + { + } + + // This template will convert to size from anything that has a cx and a cy field that are floating-point; + // a math type is required. + template + constexpr size(TilMath, const TOther& other, std::enable_if_t().cx)> && std::is_floating_point_v().cy)>, int> /*sentinel*/ = 0) : + size(TilMath::template cast(other.cx), TilMath::template cast(other.cy)) + { + } + + // This template will convert to size from anything that has a Width and a Height field that are floating-point; + // a math type is required. + template + constexpr size(TilMath, const TOther& other, std::enable_if_t().Width)> && std::is_floating_point_v().Height)>, int> /*sentinel*/ = 0) : + size(TilMath::template cast(other.Width), TilMath::template cast(other.Height)) + { + } + constexpr bool operator==(const size& other) const noexcept { return _width == other._width && diff --git a/src/til/ut_til/MathTests.cpp b/src/til/ut_til/MathTests.cpp new file mode 100644 index 0000000000..056d03edc3 --- /dev/null +++ b/src/til/ut_til/MathTests.cpp @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "til/math.h" + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +class MathTests +{ + TEST_CLASS(MathTests); + + template + struct TestCase + { + TG given; + TX expected; + }; + + template + static void _RunCases(TilMath, const std::array, N>& cases) + { + for (const auto& tc : cases) + { + VERIFY_ARE_EQUAL(tc.expected, TilMath::template cast(tc.given)); + } + } + + TEST_METHOD(Truncating) + { + std::array, 8> cases{ + TestCase{ 1., 1 }, + { 1.9, 1 }, + { -7.1, -7 }, + { -8.5, -8 }, + { PTRDIFF_MAX + 0.5, PTRDIFF_MAX }, + { PTRDIFF_MIN - 0.5, PTRDIFF_MIN }, + { INFINITY, PTRDIFF_MAX }, + { -INFINITY, PTRDIFF_MIN }, + }; + + _RunCases(til::math::truncating, cases); + + const auto fn = []() { + const auto v = til::math::details::truncating_t::cast(NAN); + }; + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + TEST_METHOD(Ceiling) + { + std::array, 8> cases{ + TestCase{ 1., 1 }, + { 1.9, 2 }, + { -7.1, -7 }, + { -8.5, -8 }, + { PTRDIFF_MAX + 0.5, PTRDIFF_MAX }, + { PTRDIFF_MIN - 0.5, PTRDIFF_MIN }, + { INFINITY, PTRDIFF_MAX }, + { -INFINITY, PTRDIFF_MIN }, + }; + + _RunCases(til::math::ceiling, cases); + + const auto fn = []() { + const auto v = til::math::details::ceiling_t::cast(NAN); + }; + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + TEST_METHOD(Flooring) + { + std::array, 8> cases{ + TestCase{ 1., 1 }, + { 1.9, 1 }, + { -7.1, -8 }, + { -8.5, -9 }, + { PTRDIFF_MAX + 0.5, PTRDIFF_MAX }, + { PTRDIFF_MIN - 0.5, PTRDIFF_MIN }, + { INFINITY, PTRDIFF_MAX }, + { -INFINITY, PTRDIFF_MIN }, + }; + + _RunCases(til::math::flooring, cases); + + const auto fn = []() { + const auto v = til::math::details::flooring_t::cast(NAN); + }; + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + TEST_METHOD(Rounding) + { + std::array, 8> cases{ + TestCase{ 1., 1 }, + { 1.9, 2 }, + { -7.1, -7 }, + { -8.5, -9 }, + { PTRDIFF_MAX + 0.5, PTRDIFF_MAX }, + { PTRDIFF_MIN - 0.5, PTRDIFF_MIN }, + { INFINITY, PTRDIFF_MAX }, + { -INFINITY, PTRDIFF_MIN }, + }; + + _RunCases(til::math::rounding, cases); + + const auto fn = []() { + const auto v = til::math::details::rounding_t::cast(NAN); + }; + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + TEST_METHOD(NormalIntegers) + { + std::array, 4> cases{ + TestCase{ 1, 1 }, + { -1, -1 }, + { PTRDIFF_MAX, INT_MAX }, + { PTRDIFF_MIN, INT_MIN }, + }; + + _RunCases(til::math::rounding, cases); + } +}; diff --git a/src/til/ut_til/PointTests.cpp b/src/til/ut_til/PointTests.cpp index bbeba7cd83..8d57e5c32c 100644 --- a/src/til/ut_til/PointTests.cpp +++ b/src/til/ut_til/PointTests.cpp @@ -604,4 +604,101 @@ class PointTests // All ptrdiff_ts fit into a float, so there's no exception tests. } + + template + struct PointTypeWith_xy + { + T x, y; + }; + template + struct PointTypeWith_XY + { + T X, Y; + }; + TEST_METHOD(CastFromFloatWithMathTypes) + { + PointTypeWith_xy xyFloatIntegral{ 1.f, 2.f }; + PointTypeWith_xy xyFloat{ 1.6f, 2.4f }; + PointTypeWith_XY XYDoubleIntegral{ 3., 4. }; + PointTypeWith_XY XYDouble{ 3.6, 4.4 }; + Log::Comment(L"0.) Ceiling"); + { + { + til::point converted{ til::math::ceiling, xyFloatIntegral }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::ceiling, xyFloat }; + VERIFY_ARE_EQUAL((til::point{ 2, 3 }), converted); + } + { + til::point converted{ til::math::ceiling, XYDoubleIntegral }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + { + til::point converted{ til::math::ceiling, XYDouble }; + VERIFY_ARE_EQUAL((til::point{ 4, 5 }), converted); + } + } + + Log::Comment(L"1.) Flooring"); + { + { + til::point converted{ til::math::flooring, xyFloatIntegral }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::flooring, xyFloat }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::flooring, XYDoubleIntegral }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + { + til::point converted{ til::math::flooring, XYDouble }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + } + + Log::Comment(L"2.) Rounding"); + { + { + til::point converted{ til::math::rounding, xyFloatIntegral }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::rounding, xyFloat }; + VERIFY_ARE_EQUAL((til::point{ 2, 2 }), converted); + } + { + til::point converted{ til::math::rounding, XYDoubleIntegral }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + { + til::point converted{ til::math::rounding, XYDouble }; + VERIFY_ARE_EQUAL((til::point{ 4, 4 }), converted); + } + } + + Log::Comment(L"3.) Truncating"); + { + { + til::point converted{ til::math::truncating, xyFloatIntegral }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::truncating, xyFloat }; + VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted); + } + { + til::point converted{ til::math::truncating, XYDoubleIntegral }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + { + til::point converted{ til::math::truncating, XYDouble }; + VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted); + } + } + } }; diff --git a/src/til/ut_til/SizeTests.cpp b/src/til/ut_til/SizeTests.cpp index dac72e6eaf..4b708edf37 100644 --- a/src/til/ut_til/SizeTests.cpp +++ b/src/til/ut_til/SizeTests.cpp @@ -515,4 +515,140 @@ class SizeTests // All ptrdiff_ts fit into a float, so there's no exception tests. } + + template + struct SizeTypeWith_XY + { + T X, Y; + }; + template + struct SizeTypeWith_cxcy + { + T cx, cy; + }; + template + struct SizeTypeWith_WidthHeight + { + T Width, Height; + }; + TEST_METHOD(CastFromFloatWithMathTypes) + { + SizeTypeWith_XY XYFloatIntegral{ 1.f, 2.f }; + SizeTypeWith_XY XYFloat{ 1.6f, 2.4f }; + SizeTypeWith_cxcy cxcyDoubleIntegral{ 3., 4. }; + SizeTypeWith_cxcy cxcyDouble{ 3.6, 4.4 }; + SizeTypeWith_WidthHeight WHDoubleIntegral{ 5., 6. }; + SizeTypeWith_WidthHeight WHDouble{ 5.6, 6.4 }; + Log::Comment(L"0.) Ceiling"); + { + { + til::size converted{ til::math::ceiling, XYFloatIntegral }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::ceiling, XYFloat }; + VERIFY_ARE_EQUAL((til::size{ 2, 3 }), converted); + } + { + til::size converted{ til::math::ceiling, cxcyDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::ceiling, cxcyDouble }; + VERIFY_ARE_EQUAL((til::size{ 4, 5 }), converted); + } + { + til::size converted{ til::math::ceiling, WHDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + { + til::size converted{ til::math::ceiling, WHDouble }; + VERIFY_ARE_EQUAL((til::size{ 6, 7 }), converted); + } + } + + Log::Comment(L"1.) Flooring"); + { + { + til::size converted{ til::math::flooring, XYFloatIntegral }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::flooring, XYFloat }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::flooring, cxcyDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::flooring, cxcyDouble }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::flooring, WHDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + { + til::size converted{ til::math::flooring, WHDouble }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + } + + Log::Comment(L"2.) Rounding"); + { + { + til::size converted{ til::math::rounding, XYFloatIntegral }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::rounding, XYFloat }; + VERIFY_ARE_EQUAL((til::size{ 2, 2 }), converted); + } + { + til::size converted{ til::math::rounding, cxcyDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::rounding, cxcyDouble }; + VERIFY_ARE_EQUAL((til::size{ 4, 4 }), converted); + } + { + til::size converted{ til::math::rounding, WHDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + { + til::size converted{ til::math::rounding, WHDouble }; + VERIFY_ARE_EQUAL((til::size{ 6, 6 }), converted); + } + } + + Log::Comment(L"3.) Truncating"); + { + { + til::size converted{ til::math::truncating, XYFloatIntegral }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::truncating, XYFloat }; + VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted); + } + { + til::size converted{ til::math::truncating, cxcyDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::truncating, cxcyDouble }; + VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted); + } + { + til::size converted{ til::math::truncating, WHDoubleIntegral }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + { + til::size converted{ til::math::truncating, WHDouble }; + VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted); + } + } + } }; diff --git a/src/til/ut_til/sources b/src/til/ut_til/sources index af602729cd..0fd583bafb 100644 --- a/src/til/ut_til/sources +++ b/src/til/ut_til/sources @@ -18,6 +18,7 @@ SOURCES = \ ColorTests.cpp \ OperatorTests.cpp \ PointTests.cpp \ + MathTests.cpp \ RectangleTests.cpp \ SizeTests.cpp \ SomeTests.cpp \ diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index d2e91bf250..011a39a768 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -13,6 +13,7 @@ +