diff --git a/components/eamxx/src/diagnostics/aodvis.cpp b/components/eamxx/src/diagnostics/aodvis.cpp index 242b810a1ea5..342e3c2f5aa7 100644 --- a/components/eamxx/src/diagnostics/aodvis.cpp +++ b/components/eamxx/src/diagnostics/aodvis.cpp @@ -47,7 +47,7 @@ void AODVis::initialize_impl(const RunType /*run_type*/) { auto nondim = ekat::units::Units::nondimensional(); const auto &grid_name = m_diagnostic_output.get_header().get_identifier().get_grid_name(); - const auto var_fill_value = constants::DefaultFillValue().value; + const auto var_fill_value = constants::fill_value; m_mask_val = m_params.get("mask_value", var_fill_value); diff --git a/components/eamxx/src/diagnostics/atm_backtend.cpp b/components/eamxx/src/diagnostics/atm_backtend.cpp index 16063211e97c..80e2566521ee 100644 --- a/components/eamxx/src/diagnostics/atm_backtend.cpp +++ b/components/eamxx/src/diagnostics/atm_backtend.cpp @@ -63,7 +63,7 @@ void AtmBackTendDiag::init_timestep(const util::TimeStamp &start_of_step) { } void AtmBackTendDiag::compute_diagnostic_impl() { - Real var_fill_value = constants::DefaultFillValue().value; + Real var_fill_value = constants::fill_value; std::int64_t dt; const auto &f = get_field_in(m_name); diff --git a/components/eamxx/src/diagnostics/field_at_pressure_level.cpp b/components/eamxx/src/diagnostics/field_at_pressure_level.cpp index 4f730d46ec21..c8ccfac3cb18 100644 --- a/components/eamxx/src/diagnostics/field_at_pressure_level.cpp +++ b/components/eamxx/src/diagnostics/field_at_pressure_level.cpp @@ -85,7 +85,7 @@ initialize_impl (const RunType /*run_type*/) // Add a field representing the mask as extra data to the diagnostic field. auto nondim = ekat::units::Units::nondimensional(); const auto& gname = fid.get_grid_name(); - m_mask_val = m_params.get("mask_value",Real(constants::DefaultFillValue::value)); + m_mask_val = m_params.get("mask_value",Real(constants::fill_value)); std::string mask_name = m_diag_name + " mask"; diff --git a/components/eamxx/src/diagnostics/tests/aodvis_test.cpp b/components/eamxx/src/diagnostics/tests/aodvis_test.cpp index 6d6021bb1ea8..47cdb8fbb088 100644 --- a/components/eamxx/src/diagnostics/tests/aodvis_test.cpp +++ b/components/eamxx/src/diagnostics/tests/aodvis_test.cpp @@ -31,7 +31,7 @@ TEST_CASE("aodvis") { using namespace ShortFieldTagsNames; using namespace ekat::units; - Real var_fill_value = constants::DefaultFillValue().value; + Real var_fill_value = constants::fill_value; Real some_limit = 0.0025; diff --git a/components/eamxx/src/diagnostics/tests/atm_backtend_test.cpp b/components/eamxx/src/diagnostics/tests/atm_backtend_test.cpp index bde27e39b3c4..734604aca2d4 100644 --- a/components/eamxx/src/diagnostics/tests/atm_backtend_test.cpp +++ b/components/eamxx/src/diagnostics/tests/atm_backtend_test.cpp @@ -65,7 +65,7 @@ TEST_CASE("atm_backtend") { REQUIRE_THROWS(diag_factory.create("AtmBackTendDiag", comm, params)); // No 'tendency_name' - Real var_fill_value = constants::DefaultFillValue().value; + Real var_fill_value = constants::fill_value; // Set time for qc and randomize its values qc.get_header().get_tracking().update_time_stamp(t0); diff --git a/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp b/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp index ec0eb5293339..6beca643ef5a 100644 --- a/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp +++ b/components/eamxx/src/physics/nudging/eamxx_nudging_process_interface.cpp @@ -344,7 +344,7 @@ void Nudging::run_impl (const double dt) const auto fl = f.get_header().get_identifier().get_layout(); const auto v = f.get_view(); - Real var_fill_value = constants::DefaultFillValue().value; + Real var_fill_value = constants::fill_value; // Query the helper field for the fill value, if not present use default if (f.get_header().has_extra_data("mask_value")) { var_fill_value = f.get_header().get_extra_data("mask_value"); diff --git a/components/eamxx/src/share/field/field_impl.hpp b/components/eamxx/src/share/field/field_impl.hpp index de8bf51fe532..249606c916bc 100644 --- a/components/eamxx/src/share/field/field_impl.hpp +++ b/components/eamxx/src/share/field/field_impl.hpp @@ -559,9 +559,8 @@ update (const Field& x, const ST alpha, const ST beta) " - x layout: " + x_l.to_string() + "\n" " - y layout: " + y_l.to_string() + "\n"); - // Determine if there is a FillValue that requires extra treatment. - bool use_fill = get_header().has_extra_data("mask_value") or - x.get_header().has_extra_data("mask_value"); + // Determine if the RHS has fill value entries that require extra treatment + bool use_fill = x.get_header().has_extra_data("mask_value"); if (dt==DataType::IntType) { if (use_fill) { @@ -609,18 +608,13 @@ update_impl (const Field& x, const ST alpha, const ST beta) const auto& layout = x.get_header().get_identifier().get_layout(); const auto& dims = layout.dims(); - ST fill_val = 0; + XST fill_val = 0; if constexpr (use_fill) { - if (get_header().has_extra_data("mask_value")) { - fill_val = get_header().get_extra_data("mask_value"); - } else if (x.get_header().has_extra_data("mask_value")) { - fill_val = static_cast(x.get_header().get_extra_data("mask_value")); - } else { - EKAT_ERROR_MSG ("Error! Field::update_impl called with use_fill,\n" - " but neither *this nor x has mask_value extra data.\n" - " - *this name: " + name() + "\n" - " - x name: " + x.name() + "\n"); - } + EKAT_REQUIRE_MSG (x.get_header().has_extra_data("mask_value"), + "Error! Field::update_impl called with use_fill, but x does not have mask_value extra data.\n" + " - *this name: " + name() + "\n" + " - x name: " + x.name() + "\n"); + fill_val = x.get_header().get_extra_data("mask_value"); } // Must handle the case where one of the two views is strided (or both) diff --git a/components/eamxx/src/share/field/field_impl_details.hpp b/components/eamxx/src/share/field/field_impl_details.hpp index 4a68d4081772..2965513a135a 100644 --- a/components/eamxx/src/share/field/field_impl_details.hpp +++ b/components/eamxx/src/share/field/field_impl_details.hpp @@ -114,7 +114,7 @@ struct CombineViewsHelper { template void cvh (LhsView lhs, RhsView rhs, - ST alpha, ST beta, typename LhsView::traits::value_type fill_val, + ST alpha, ST beta, typename RhsView::traits::value_type fill_val, const std::vector& dims) { CombineViewsHelper helper; diff --git a/components/eamxx/src/share/io/eamxx_scorpio_interface.cpp b/components/eamxx/src/share/io/eamxx_scorpio_interface.cpp index 4bacad593406..15be1e9c5c41 100644 --- a/components/eamxx/src/share/io/eamxx_scorpio_interface.cpp +++ b/components/eamxx/src/share/io/eamxx_scorpio_interface.cpp @@ -159,6 +159,7 @@ int nctype () { } else { EKAT_ERROR_MSG ("Error! Unrecognized/unsupported data type.\n"); } + return PIO_NAT; } template diff --git a/components/eamxx/src/share/io/scorpio_output.hpp b/components/eamxx/src/share/io/scorpio_output.hpp index f4c148d6e190..70257e25cc50 100644 --- a/components/eamxx/src/share/io/scorpio_output.hpp +++ b/components/eamxx/src/share/io/scorpio_output.hpp @@ -219,7 +219,7 @@ class AtmosphereOutput // NetCDF: Numeric conversion not representable // Also, by default, don't pick max float, to avoid any overflow if the value // is used inside other calculation and/or remap. - float m_fill_value = constants::DefaultFillValue().value; + float m_fill_value = constants::fill_value; bool m_add_time_dim; bool m_track_avg_cnt = false; diff --git a/components/eamxx/src/share/io/tests/io_basic.cpp b/components/eamxx/src/share/io/tests/io_basic.cpp index 85730b5b6401..98f92c89bd98 100644 --- a/components/eamxx/src/share/io/tests/io_basic.cpp +++ b/components/eamxx/src/share/io/tests/io_basic.cpp @@ -274,7 +274,7 @@ void read (const std::string& avg_type, const std::string& freq_units, // Check that the expected metadata was appropriately set for each variable for (const auto& fn: fnames) { auto att_fill = scorpio::get_attribute(filename,fn,"_FillValue"); - REQUIRE(att_fill==constants::DefaultFillValue().value); + REQUIRE(att_fill==constants::fill_value); auto att_str = scorpio::get_attribute(filename,fn,"test"); REQUIRE (att_str==fn); diff --git a/components/eamxx/src/share/io/tests/io_filled.cpp b/components/eamxx/src/share/io/tests/io_filled.cpp index 35d89b5e1d32..4257338f31d5 100644 --- a/components/eamxx/src/share/io/tests/io_filled.cpp +++ b/components/eamxx/src/share/io/tests/io_filled.cpp @@ -27,7 +27,7 @@ namespace scream { constexpr int num_output_steps = 5; -constexpr Real FillValue = constants::DefaultFillValue().value; +constexpr Real FillValue = constants::fill_value; constexpr Real fill_threshold = 0.5; void set (const Field& f, const double v) { @@ -255,7 +255,7 @@ void read (const std::string& avg_type, const std::string& freq_units, for (const auto& fn: fnames) { // NOTE: use float, since default fp_precision for I/O is 'single' auto att_fill = scorpio::get_attribute(filename,fn,"_FillValue"); - REQUIRE(att_fill==constants::DefaultFillValue().value); + REQUIRE(att_fill==constants::fill_value); } } diff --git a/components/eamxx/src/share/io/tests/io_remap_test.cpp b/components/eamxx/src/share/io/tests/io_remap_test.cpp index 7deb0f817f53..82ec9201e0ea 100644 --- a/components/eamxx/src/share/io/tests/io_remap_test.cpp +++ b/components/eamxx/src/share/io/tests/io_remap_test.cpp @@ -283,7 +283,7 @@ TEST_CASE("io_remap_test","io_remap_test") { // Note, the vertical remapper defaults to a mask value of std numeric limits scaled by 0.1; const float mask_val = vert_remap_control.isParameter("Fill Value") - ? vert_remap_control.get("Fill Value") : constants::DefaultFillValue().value; + ? vert_remap_control.get("Fill Value") : constants::fill_value; print (" -> vertical remap ... \n",io_comm); auto gm_vert = get_test_gm(io_comm,ncols_src,nlevs_tgt); auto grid_vert = gm_vert->get_grid("point_grid"); @@ -353,7 +353,7 @@ TEST_CASE("io_remap_test","io_remap_test") { // Note, the vertical remapper defaults to a mask value of std numeric limits scaled by 0.1; const float mask_val = horiz_remap_control.isParameter("Fill Value") - ? horiz_remap_control.get("Fill Value") : constants::DefaultFillValue().value; + ? horiz_remap_control.get("Fill Value") : constants::fill_value; print (" -> horizontal remap ... \n",io_comm); auto gm_horiz = get_test_gm(io_comm,ncols_tgt,nlevs_src); auto grid_horiz = gm_horiz->get_grid("point_grid"); @@ -439,7 +439,7 @@ TEST_CASE("io_remap_test","io_remap_test") // --- Vertical + Horizontal Remapping --- { const float mask_val = vert_horiz_remap_control.isParameter("Fill Value") - ? vert_horiz_remap_control.get("Fill Value") : constants::DefaultFillValue().value; + ? vert_horiz_remap_control.get("Fill Value") : constants::fill_value; print (" -> vertical + horizontal remap ... \n",io_comm); auto gm_vh = get_test_gm(io_comm,ncols_tgt,nlevs_tgt); auto grid_vh = gm_vh->get_grid("point_grid"); diff --git a/components/eamxx/src/share/io/tests/output_restart.cpp b/components/eamxx/src/share/io/tests/output_restart.cpp index 9c5cffa7826a..0b1fd7fcab01 100644 --- a/components/eamxx/src/share/io/tests/output_restart.cpp +++ b/components/eamxx/src/share/io/tests/output_restart.cpp @@ -27,7 +27,7 @@ namespace scream { -constexpr Real FillValue = constants::DefaultFillValue().value; +constexpr Real FillValue = constants::fill_value; std::shared_ptr get_test_fm(const std::shared_ptr& grid); diff --git a/components/eamxx/src/share/tests/CMakeLists.txt b/components/eamxx/src/share/tests/CMakeLists.txt index df3f7322509b..12749cbd00e4 100644 --- a/components/eamxx/src/share/tests/CMakeLists.txt +++ b/components/eamxx/src/share/tests/CMakeLists.txt @@ -5,6 +5,9 @@ if (NOT SCREAM_ONLY_GENERATE_BASELINES) # Test utils CreateUnitTest(utils "utils_tests.cpp") + # Test combine operations + CreateUnitTest(combine_ops "combine_ops.cpp") + # Test column ops CreateUnitTest(column_ops "column_ops.cpp") diff --git a/components/eamxx/src/share/tests/column_ops.cpp b/components/eamxx/src/share/tests/column_ops.cpp index c4cf3a4e7e3e..29aee88ee320 100644 --- a/components/eamxx/src/share/tests/column_ops.cpp +++ b/components/eamxx/src/share/tests/column_ops.cpp @@ -7,37 +7,6 @@ namespace { -TEST_CASE ("combine_ops") { - using namespace scream; - using pack_type = ekat::Pack; - - constexpr auto Replace = CombineMode::Replace; - constexpr auto Update = CombineMode::Update; - constexpr auto Multiply = CombineMode::Multiply; - constexpr auto Divide = CombineMode::Divide; - - const pack_type two (2.0); - const pack_type four (4.0); - const pack_type six (6.0); - const pack_type ten (10.0); - pack_type x; - - x = two; - combine(two,x,1,0); - REQUIRE ( (x==two).all() ); - - combine(two,x,2.0,1.0); - REQUIRE ( (x==six).all() ); - - x = two; - combine(two,x,1,1); - REQUIRE ( (x==four).all() ); - - x = four; - combine(two,x,1,1); - REQUIRE ( (x==two).all() ); -} - TEST_CASE("column_ops_ps_1") { using namespace scream; using device_type = DefaultDevice; diff --git a/components/eamxx/src/share/tests/combine_ops.cpp b/components/eamxx/src/share/tests/combine_ops.cpp new file mode 100644 index 000000000000..e872d6027a0b --- /dev/null +++ b/components/eamxx/src/share/tests/combine_ops.cpp @@ -0,0 +1,74 @@ +#include + +#include "share/util/eamxx_combine_ops.hpp" +#include "share/util/eamxx_universal_constants.hpp" +#include "share/eamxx_types.hpp" + +#include "ekat/ekat_pack.hpp" + +#include + +namespace { + +TEST_CASE ("combine_ops") { + using namespace scream; + using pack_type = ekat::Pack; + + constexpr auto Replace = CombineMode::Replace; + constexpr auto Update = CombineMode::Update; + constexpr auto Multiply = CombineMode::Multiply; + constexpr auto Divide = CombineMode::Divide; + constexpr auto Max = CombineMode::Max; + constexpr auto Min = CombineMode::Min; + constexpr auto fv_val = constants::fill_value; + + const pack_type two (2.0); + const pack_type four (4.0); + const pack_type six (6.0); + const pack_type ten (10.0); + const pack_type fv (constants::fill_value); + const pack_type fv_times_ten (constants::fill_value * 10); + + pack_type x; + + x = two; + combine(two,x,1,0); + REQUIRE ( (x==two).all() ); + + combine(two,x,2.0,1.0); + REQUIRE ( (x==six).all() ); + fill_aware_combine(fv,x,fv_val,2.0,1.0); + if (not (x==six).all() ) { + std::cout << "x: " << x << "\n"; + std::cout << " x[0]: " << std::setprecision(18) << x[0] << "\n"; + std::cout << "fv[0]: " << std::setprecision(18) << fv[0] << "\n"; + } + REQUIRE ( (x==six).all() ); + + x = two; + combine(two,x,1,1); + REQUIRE ( (x==four).all() ); + fill_aware_combine(fv,x,fv_val,1,1); + REQUIRE ( (x==four).all() ); + + x = four; + combine(two,x,1,1); + REQUIRE ( (x==two).all() ); + fill_aware_combine(fv,x,fv_val,1,1); + REQUIRE ( (x==two).all() ); + + x = two; + combine(four,x,1,1); + REQUIRE ( (x==four).all() ); + fill_aware_combine(fv,x,fv_val,1,1); + REQUIRE ( (x==four).all() ); + + x = four; + combine(two,x,1,1); + REQUIRE ( (x==two).all() ); + x = fv_times_ten; + fill_aware_combine(fv,x,fv_val,1,1); + REQUIRE ( (x==fv_times_ten).all() ); +} + +} // anonymous namespace diff --git a/components/eamxx/src/share/tests/field_tests.cpp b/components/eamxx/src/share/tests/field_tests.cpp index 0edb7708a67b..de1f84bfa182 100644 --- a/components/eamxx/src/share/tests/field_tests.cpp +++ b/components/eamxx/src/share/tests/field_tests.cpp @@ -887,6 +887,13 @@ TEST_CASE ("update") { Field f4 = two.clone(); f4.min(f3); REQUIRE (views_are_equal(f3, f4)); + + // Check that updating with rhs==fill_value ignores the rhs + f3.deep_copy(constants::fill_value); + f3.get_header().set_extra_data("mask_value",constants::fill_value); + f2.deep_copy(1.0); + f2.max(f3); + REQUIRE (views_are_equal(f2,one)); } SECTION ("int") { @@ -904,6 +911,13 @@ TEST_CASE ("update") { Field f4 = two.clone(); f4.min(f3); REQUIRE (views_are_equal(f3, f4)); + + // Check that updating with rhs==fill_value ignores the rhs + f3.deep_copy(constants::fill_value); + f3.get_header().set_extra_data("mask_value",constants::fill_value); + f2.deep_copy(1); + f2.max(f3); + REQUIRE (views_are_equal(f2,one)); } } @@ -949,6 +963,19 @@ TEST_CASE ("update") { // Same, but we discard current content of f3 f3.update(f_real,2,0); REQUIRE (views_are_equal(f3,f2)); + + // Check that updating with rhs==fill_value ignores the rhs + Field one = f_real.clone(); + one.deep_copy(1.0); + + f3.deep_copy(constants::fill_value); + f3.get_header().set_extra_data("mask_value",constants::fill_value); + f2.deep_copy(1.0); + f2.update(f3,1,1); + if (not views_are_equal(f2,one)) { + print_field_hyperslab(f2); + } + REQUIRE (views_are_equal(f2,one)); } SECTION ("int") { @@ -968,6 +995,16 @@ TEST_CASE ("update") { // Same, but we discard current content of f3 f3.update(f_int,2,0); REQUIRE (views_are_equal(f3,f2)); + + // Check that updating with rhs==fill_value ignores the rhs + Field one = f_int.clone(); + one.deep_copy(1); + + f3.deep_copy(constants::fill_value); + f3.get_header().set_extra_data("mask_value",constants::fill_value); + f2.deep_copy(1); + f2.update(f3,1,1); + REQUIRE (views_are_equal(f2,one)); } } } diff --git a/components/eamxx/src/share/tests/utils_tests.cpp b/components/eamxx/src/share/tests/utils_tests.cpp index 472a8ec0b59f..4dac824830e7 100644 --- a/components/eamxx/src/share/tests/utils_tests.cpp +++ b/components/eamxx/src/share/tests/utils_tests.cpp @@ -7,6 +7,16 @@ #include "share/util/eamxx_setup_random_test.hpp" #include "share/eamxx_config.hpp" +TEST_CASE("fill_value") { + using namespace scream::constants; + + // Ensure we have the SAME numerical value for both float and double + auto fv_d = fill_value; + auto fv_f = fill_value; + + REQUIRE (fv_d==fv_f); +} + TEST_CASE("contiguous_superset") { using namespace scream; diff --git a/components/eamxx/src/share/util/eamxx_combine_ops.hpp b/components/eamxx/src/share/util/eamxx_combine_ops.hpp index 8977d800a136..b4e550eafee3 100644 --- a/components/eamxx/src/share/util/eamxx_combine_ops.hpp +++ b/components/eamxx/src/share/util/eamxx_combine_ops.hpp @@ -5,6 +5,7 @@ #include #include +#include // For KOKKOS_INLINE_FUNCTION #include @@ -75,28 +76,59 @@ void combine (const ScalarIn& newVal, ScalarOut& result, break; } } -/* Special version of combine that takes a mask into account */ + +// Special version of combine that ignores newVal if newVal==fill_value. +// Replace is the only combine mode that is allowed to consider fill_val values. +// This is b/c it's the only way we can use this function inside Field method/utils +// in order to set all entries of a Field to fill_val. You can also think of fill_val +// as a special number for which the arithmetic operations are not defined. +// All CM except for Replace involve an arithmetic op between of two numbers, +// so combining with fill_val makes no sense. However, it makes sense to set +// an output variable to fill_val. template::scalar_type> KOKKOS_FORCEINLINE_FUNCTION -void fill_aware_combine (const ScalarIn& newVal, ScalarOut& result, const ScalarOut fill_val, - const CoeffType alpha, const CoeffType beta) +std::enable_if_t::is_simd or + ekat::ScalarTraits::is_simd> +fill_aware_combine (const ScalarIn& newVal, ScalarOut& result, + const typename ekat::ScalarTraits::scalar_type fill_val, + const CoeffType alpha, const CoeffType beta) { - switch (CM) { - case CombineMode::Replace: - combine(newVal,result,alpha,beta); - break; - case CombineMode::Update: - case CombineMode::Multiply: - case CombineMode::Divide: - case CombineMode::Max: - case CombineMode::Min: - if (newVal != fill_val) - combine(newVal,result,alpha,beta); - break; - - default: - EKAT_KERNEL_ERROR_MSG("Unsupported combine mode for 'fill_aware_combine' overload"); + if constexpr (CM==CombineMode::Replace) { + return combine(newVal,result,alpha,beta); + } + + // The where object will perform the assignment ONLY where the mask is true + auto where = ekat::where(newVal!=fill_val,result); + if (where.any()) { + // TODO: I thought about doing the switch manually, and do stuff like (e.g., for Update) + // where *= beta; + // where += alpha*newVal + // but there is no packed version of where.max(rhs), only a scalar version + // (meaning a version where rhs is a scalar, not a pack). + // If ekat::where_expression ever implements a packed overload for max/min, + // we can get rid of the temporary by doing a manual switch. + auto tmp = result; + combine(newVal,tmp,alpha,beta); + where = tmp; + } +} + +template::scalar_type> +KOKKOS_FORCEINLINE_FUNCTION +std::enable_if_t::is_simd and + not ekat::ScalarTraits::is_simd> +fill_aware_combine (const ScalarIn& newVal, ScalarOut& result, + const typename ekat::ScalarTraits::scalar_type fill_val, + const CoeffType alpha, const CoeffType beta) +{ + if constexpr (CM==CombineMode::Replace) { + return combine(newVal,result,alpha,beta); + } + + if (newVal!=fill_val) { + combine(newVal,result,alpha,beta); } } diff --git a/components/eamxx/src/share/util/eamxx_universal_constants.hpp b/components/eamxx/src/share/util/eamxx_universal_constants.hpp index 9937bbf595fe..5560bdc8cac6 100644 --- a/components/eamxx/src/share/util/eamxx_universal_constants.hpp +++ b/components/eamxx/src/share/util/eamxx_universal_constants.hpp @@ -12,15 +12,15 @@ constexpr int seconds_per_day = 86400; constexpr int days_per_nonleap_year = 365; // Universal fill value for variables -// TODO: When we switch to supporting C++17 we can use a simple `inline constexpr` rather than a struct +// NOTE: for floating point numbers, use the SAME numerical value, so that +// we don't need to be aware of the precision of variables when checking +// against fill_value (e.g., when reading in double data that was saved +// in single precision) template -struct DefaultFillValue { - static constexpr bool is_float = std::is_floating_point::value; - static constexpr bool is_int = std::is_integral::value; - static constexpr T value = is_int ? std::numeric_limits::max() / 2 : - is_float ? std::numeric_limits::max() / 1e5 : std::numeric_limits::max(); - -}; +constexpr T fill_value = + std::is_integral_v ? std::numeric_limits::max() / 2 + : std::is_floating_point_v ? std::numeric_limits::max() / static_cast(1e5) + : std::numeric_limits::max(); } // namespace constants