diff --git a/.gitmodules b/.gitmodules index a46c5b283..f90bc2c3e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -39,3 +39,6 @@ [submodule "deps/type_safe"] path = deps/type_safe url = https://github.com/foonathan/type_safe +[submodule "deps/int128"] + path = deps/int128 + url = https://github.com/cppalliance/int128 diff --git a/deps/SCsub b/deps/SCsub index 07cddacdc..80b14f616 100644 --- a/deps/SCsub +++ b/deps/SCsub @@ -251,6 +251,14 @@ def build_type_safe(env): env.exposed_includes += env.type_safe["INCPATH"] +def build_int128(env): + include_path = "int128/include" + env.int128 = {} + env.int128["INCPATH"] = [env.Dir(include_path)] + env.Append(CPPPATH=env.int128["INCPATH"]) + env.exposed_includes += env.int128["INCPATH"] + + def link_tbb(env): import sys @@ -271,4 +279,5 @@ build_memory(env) build_spdlog(env) build_xoshiro(env) build_type_safe(env) +build_int128(env) link_tbb(env) diff --git a/deps/int128 b/deps/int128 new file mode 160000 index 000000000..ea0f85e69 --- /dev/null +++ b/deps/int128 @@ -0,0 +1 @@ +Subproject commit ea0f85e693767ae667e26d66022d6ed68d3425de diff --git a/src/openvic-simulation/core/template/Concepts.hpp b/src/openvic-simulation/core/template/Concepts.hpp index 3063bfb9e..1dd099f8f 100644 --- a/src/openvic-simulation/core/template/Concepts.hpp +++ b/src/openvic-simulation/core/template/Concepts.hpp @@ -108,9 +108,12 @@ namespace OpenVic { { t.get_name() } -> std::same_as; }; + template + concept is_strongly_typed = derived_from_specialization_of; + template concept has_index = requires { typename T::index_t; } && - derived_from_specialization_of && requires { + is_strongly_typed && requires { static_cast( static_cast().index)>>(std::declval().index) ); @@ -222,8 +225,8 @@ namespace OpenVic { { lhs /= rhs } -> std::same_as; }; - template - concept mul_add_assignable = requires(Lhs& lhs, const A a, const B b) { - { lhs += a * b } -> std::same_as; + template + concept equalable = requires(Lhs const& lhs, Rhs const& rhs) { + { lhs == rhs } -> std::convertible_to; }; } diff --git a/src/openvic-simulation/country/CountryInstance.cpp b/src/openvic-simulation/country/CountryInstance.cpp index 350073e53..27f3434e9 100644 --- a/src/openvic-simulation/country/CountryInstance.cpp +++ b/src/openvic-simulation/country/CountryInstance.cpp @@ -43,7 +43,8 @@ #include "openvic-simulation/types/Date.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" +#include "openvic-simulation/population/PopSum.hpp" #include "openvic-simulation/types/UnitBranchType.hpp" #include "openvic-simulation/utility/Containers.hpp" #include "openvic-simulation/utility/Logger.hpp" @@ -1341,8 +1342,8 @@ void CountryInstance::_update_budget() { OpenVic immediately updates both. */ - pop_size_t total_non_colonial_population = 0; - pop_size_t administrators = 0; + pop_sum_t total_non_colonial_population = 0; + pop_sum_t administrators = 0; for (State const* const state_ptr : states) { if (state_ptr == nullptr) { continue; @@ -1353,7 +1354,7 @@ void CountryInstance::_update_budget() { continue; } - IndexedFlatMap const& state_population_by_type = state.get_population_by_type(); + IndexedFlatMap const& state_population_by_type = state.get_population_by_type(); for (auto const& [pop_type, size] : state_population_by_type) { if (pop_type.is_administrator) { @@ -1367,18 +1368,24 @@ void CountryInstance::_update_budget() { administrative_efficiency_from_administrators.set(fixed_point_t::_1); administrator_percentage.set(fixed_point_t::_0); } else { - administrator_percentage.set(fixed_point_t(administrators) / total_non_colonial_population); - - const fixed_point_t desired_administrators = desired_administrator_percentage.get_untracked() * total_non_colonial_population; - const fixed_point_t administrative_efficiency_from_administrators_unclamped = std::min( - fixed_point_t::mul_div( - administrators, - fixed_point_t::_1 + get_modifier_effect_value(*modifier_effect_cache.get_administrative_efficiency()), - desired_administrators - ) - * (fixed_point_t::_1 + get_modifier_effect_value(*modifier_effect_cache.get_administrative_efficiency_modifier())), - fixed_point_t::_1 + administrator_percentage.set(fixed_point_t::from_fraction(administrators, total_non_colonial_population)); + + const pop_sum_t desired_administrators = fixed_point_t::multiply_truncate( + total_non_colonial_population, + desired_administrator_percentage.get_untracked() + ); + const pop_sum_t effective_administrators = fixed_point_t::multiply_truncate( + administrators, + fixed_point_t::_1 + get_modifier_effect_value(*modifier_effect_cache.get_administrative_efficiency()) ); + const fixed_point_t administrative_efficiency_from_administrators_unclamped = + desired_administrators == 0 + ? fixed_point_t::_1 + : std::min( + fixed_point_t::from_fraction(effective_administrators, desired_administrators) + * (fixed_point_t::_1 + get_modifier_effect_value(*modifier_effect_cache.get_administrative_efficiency_modifier())), + fixed_point_t::_1 + ); administrative_efficiency_from_administrators.set( game_rules_manager.get_prevent_negative_administration_efficiency() @@ -1387,36 +1394,42 @@ void CountryInstance::_update_budget() { ); } - fixed_point_t projected_administration_spending_unscaled_by_slider_running_total = 0; - fixed_point_t projected_education_spending_unscaled_by_slider_running_total = 0; - fixed_point_t projected_military_spending_unscaled_by_slider_running_total = 0; - fixed_point_t projected_pensions_spending_unscaled_by_slider_running_total = 0; - fixed_point_t projected_unemployment_subsidies_spending_unscaled_by_slider_running_total = 0; + int64_t projected_administration_spending_unscaled_by_slider_running_total = 0; + int64_t projected_education_spending_unscaled_by_slider_running_total = 0; + int64_t projected_military_spending_unscaled_by_slider_running_total = 0; + int64_t projected_pensions_spending_unscaled_by_slider_running_total = 0; + int64_t projected_unemployment_subsidies_spending_unscaled_by_slider_running_total = 0; - for (auto const& [pop_type, size] : get_population_by_type()) { - projected_administration_spending_unscaled_by_slider_running_total += size * administration_salary_base_by_pop_type.at(pop_type).get_untracked(); - projected_education_spending_unscaled_by_slider_running_total += size * education_salary_base_by_pop_type.at(pop_type).get_untracked(); - projected_military_spending_unscaled_by_slider_running_total += size * military_salary_base_by_pop_type.at(pop_type).get_untracked(); - projected_pensions_spending_unscaled_by_slider_running_total += size * calculate_pensions_base(pop_type); - projected_unemployment_subsidies_spending_unscaled_by_slider_running_total += get_unemployed_pops_by_type(pop_type) - * calculate_unemployment_subsidies_base(pop_type); - } - - projected_administration_spending_unscaled_by_slider.set( - projected_administration_spending_unscaled_by_slider_running_total / Pop::size_denominator - ); - projected_education_spending_unscaled_by_slider.set( - projected_education_spending_unscaled_by_slider_running_total / Pop::size_denominator - ); - projected_military_spending_unscaled_by_slider.set( - projected_military_spending_unscaled_by_slider_running_total / Pop::size_denominator - ); - projected_pensions_spending_unscaled_by_slider.set( - projected_pensions_spending_unscaled_by_slider_running_total / Pop::size_denominator - ); - projected_unemployment_subsidies_spending_unscaled_by_slider.set( - projected_unemployment_subsidies_spending_unscaled_by_slider_running_total / Pop::size_denominator - ); + for (auto const& [pop_type, pop_size] : get_population_by_type()) { + const int64_t size = type_safe::get(pop_size); + projected_administration_spending_unscaled_by_slider_running_total += size * administration_salary_base_by_pop_type.at(pop_type).get_untracked().get_raw_value(); + projected_education_spending_unscaled_by_slider_running_total += size * education_salary_base_by_pop_type.at(pop_type).get_untracked().get_raw_value(); + projected_military_spending_unscaled_by_slider_running_total += size * military_salary_base_by_pop_type.at(pop_type).get_untracked().get_raw_value(); + projected_pensions_spending_unscaled_by_slider_running_total += size * calculate_pensions_base(pop_type).get_raw_value(); + projected_unemployment_subsidies_spending_unscaled_by_slider_running_total += type_safe::get(get_unemployed_pops_by_type(pop_type)) + * calculate_unemployment_subsidies_base(pop_type).get_raw_value(); + } + + projected_administration_spending_unscaled_by_slider.set(fixed_point_t::from_fraction( + projected_administration_spending_unscaled_by_slider_running_total, + type_safe::get(Pop::size_denominator) + )); + projected_education_spending_unscaled_by_slider.set(fixed_point_t::from_fraction( + projected_education_spending_unscaled_by_slider_running_total, + type_safe::get(Pop::size_denominator) + )); + projected_military_spending_unscaled_by_slider.set(fixed_point_t::from_fraction( + projected_military_spending_unscaled_by_slider_running_total, + type_safe::get(Pop::size_denominator) + )); + projected_pensions_spending_unscaled_by_slider.set(fixed_point_t::from_fraction( + projected_pensions_spending_unscaled_by_slider_running_total, + type_safe::get(Pop::size_denominator) + )); + projected_unemployment_subsidies_spending_unscaled_by_slider.set(fixed_point_t::from_fraction( + projected_unemployment_subsidies_spending_unscaled_by_slider_running_total, + type_safe::get(Pop::size_denominator) + )); } fixed_point_t CountryInstance::calculate_pensions_base(PopType const& pop_type) { @@ -1498,9 +1511,18 @@ void CountryInstance::_update_population() { for (auto const& [pop_type, pop_size] : get_population_by_type()) { if (pop_type.research_leadership_optimum > 0 && pop_size > 0) { - const fixed_point_t factor = std::min( - pop_size / (get_total_population() * pop_type.research_leadership_optimum), fixed_point_t::_1 + const pop_sum_t optimum_size = fixed_point_t::multiply_truncate( + get_total_population(), + pop_type.research_leadership_optimum ); + const fixed_point_t factor = optimum_size == 0 + ? fixed_point_t::_1 + : std::min( + fixed_point_t::from_fraction( + pop_size, + optimum_size + ), fixed_point_t::_1 + ); if (pop_type.research_points != 0) { const fixed_point_t research_points = pop_type.research_points * factor; @@ -1634,11 +1656,9 @@ void CountryInstance::_update_military() { naval_unit_start_experience += get_modifier_effect_value(*modifier_effect_cache.get_naval_unit_start_experience()); recruit_time = fixed_point_t::_1 + get_modifier_effect_value(*modifier_effect_cache.get_unit_recruitment_time()); - combat_width = combat_width_t( - ( - type_safe::get(military_defines.get_base_combat_width()) - + get_modifier_effect_value(*modifier_effect_cache.get_combat_width_additive()) - ).floor>() + combat_width = fixed_point_t::multiply_truncate( + military_defines.get_base_combat_width(), + get_modifier_effect_value(*modifier_effect_cache.get_combat_width_additive()) ); dig_in_cap = get_modifier_effect_value(*modifier_effect_cache.get_dig_in_cap()).floor(); military_tactics = military_defines.get_base_military_tactics() + diff --git a/src/openvic-simulation/dataloader/Dataloader.cpp b/src/openvic-simulation/dataloader/Dataloader.cpp index d114d2851..887fabdf0 100644 --- a/src/openvic-simulation/dataloader/Dataloader.cpp +++ b/src/openvic-simulation/dataloader/Dataloader.cpp @@ -240,11 +240,11 @@ string_set_t Dataloader::lookup_dirs_in_dir(std::string_view path) const { return ret; } -template Parser, bool (*parse_func)(Parser&)> +template Parser, bool (*parse_func)(Parser&)> static Parser _run_ovdl_parser(fs::path const& path) { Parser parser; memory::string buffer; - auto error_log_stream = detail::make_callback_stream( + auto error_log_stream = ovdl::detail::make_callback_stream( [](void const* s, std::streamsize n, void* user_data) -> std::streamsize { if (s != nullptr && n > 0 && user_data != nullptr) { static_cast(user_data)->append(static_cast(s), n); diff --git a/src/openvic-simulation/dataloader/NodeTools.hpp b/src/openvic-simulation/dataloader/NodeTools.hpp index b2e0b15df..1ab1a890f 100644 --- a/src/openvic-simulation/dataloader/NodeTools.hpp +++ b/src/openvic-simulation/dataloader/NodeTools.hpp @@ -199,21 +199,49 @@ using namespace std::string_view_literals; return expect_uint(callback, base); } + template T, typename AsT = type_safe::underlying_type> + NodeCallback auto expect_strong_typedef(callback_t& callback, int base = 10) { + if constexpr (std::unsigned_integral) { + return expect_uint64( + [callback](uint64_t val) mutable -> bool { + if (val <= static_cast(std::numeric_limits::max())) { + return callback(T(val)); + } + spdlog::error_s( + "Invalid uint: {} (valid range: [0, {}])", val, + static_cast(std::numeric_limits::max()) + ); + return false; + }, + base + ); + } else { + return expect_int64( + [callback](int64_t val) mutable -> bool { + if (val >= static_cast(std::numeric_limits::min()) && + val <= static_cast(std::numeric_limits::max())) { + return callback(T(val)); + } + spdlog::error_s( + "Invalid int: {} (valid range: [{}, {}])", val, + static_cast(std::numeric_limits::min()), + static_cast(std::numeric_limits::max()) + ); + return false; + }, + base + ); + } + } + template T, typename AsT = type_safe::underlying_type> + NodeCallback auto expect_strong_typedef(callback_t&& callback, int base = 10) { + return expect_strong_typedef(callback, base); + } + template T> requires std::unsigned_integral> NodeCallback auto expect_index(callback_t& callback, int base = 10) { - using underlying_type = type_safe::underlying_type; - - return expect_uint64([callback](uint64_t val) mutable -> bool { - if (val <= static_cast(std::numeric_limits::max())) { - return callback(T(val)); - } - spdlog::error_s( - "Invalid uint: {} (valid range: [0, {}])", - val, static_cast(std::numeric_limits::max()) - ); - return false; - }, base); + return expect_strong_typedef(callback, base); } template T> requires std::unsigned_integral> diff --git a/src/openvic-simulation/defines/MilitaryDefines.cpp b/src/openvic-simulation/defines/MilitaryDefines.cpp index 8c527f7ad..74ad976aa 100644 --- a/src/openvic-simulation/defines/MilitaryDefines.cpp +++ b/src/openvic-simulation/defines/MilitaryDefines.cpp @@ -1,7 +1,5 @@ #include "MilitaryDefines.hpp" -#include - #include "openvic-simulation/military/CombatWidth.hpp" #include @@ -20,12 +18,9 @@ node_callback_t MilitaryDefines::expect_defines() { "DIG_IN_INCREASE_EACH_DAYS", ONE_EXACTLY, expect_days(assign_variable_callback(dig_in_increase_each_days)), "REINFORCE_SPEED", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(reinforce_speed)), "COMBAT_DIFFICULTY_IMPACT", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(combat_difficulty_impact)), - "BASE_COMBAT_WIDTH", ONE_EXACTLY, expect_int([this](int8_t val)->bool{ - base_combat_width = combat_width_t(static_cast>(val)); - return true; - }), - "POP_MIN_SIZE_FOR_REGIMENT", ONE_EXACTLY, expect_uint(assign_variable_callback(min_pop_size_for_regiment)), - "POP_SIZE_PER_REGIMENT", ONE_EXACTLY, expect_uint(assign_variable_callback(pop_size_per_regiment)), + "BASE_COMBAT_WIDTH", ONE_EXACTLY, expect_strong_typedef(assign_variable_callback(base_combat_width)), + "POP_MIN_SIZE_FOR_REGIMENT", ONE_EXACTLY, expect_strong_typedef(assign_variable_callback(min_pop_size_for_regiment)), + "POP_SIZE_PER_REGIMENT", ONE_EXACTLY, expect_strong_typedef(assign_variable_callback(pop_size_per_regiment)), "SOLDIER_TO_POP_DAMAGE", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(soldier_to_pop_damage)), "LAND_SPEED_MODIFIER", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(land_speed_modifier)), "NAVAL_SPEED_MODIFIER", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(naval_speed_modifier)), diff --git a/src/openvic-simulation/defines/MilitaryDefines.hpp b/src/openvic-simulation/defines/MilitaryDefines.hpp index 3ed13e9b9..c104893c0 100644 --- a/src/openvic-simulation/defines/MilitaryDefines.hpp +++ b/src/openvic-simulation/defines/MilitaryDefines.hpp @@ -4,7 +4,7 @@ #include "openvic-simulation/military/CombatWidth.hpp" #include "openvic-simulation/types/Date.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { diff --git a/src/openvic-simulation/defines/PopsDefines.cpp b/src/openvic-simulation/defines/PopsDefines.cpp index ea7064af4..5717d26ec 100644 --- a/src/openvic-simulation/defines/PopsDefines.cpp +++ b/src/openvic-simulation/defines/PopsDefines.cpp @@ -75,8 +75,8 @@ node_callback_t PopsDefines::expect_defines() { "MOVEMENT_SUPPORT_UH_FACTOR", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(movement_support_uh_factor)), "REBEL_OCCUPATION_STRENGTH_BONUS", ONE_EXACTLY, expect_fixed_point(assign_variable_callback(rebel_occupation_strength_bonus)), - "LARGE_POPULATION_LIMIT", ONE_EXACTLY, expect_uint(assign_variable_callback(large_population_limit)), + "LARGE_POPULATION_LIMIT", ONE_EXACTLY, expect_strong_typedef(assign_variable_callback(large_population_limit)), "LARGE_POPULATION_INFLUENCE_PENALTY_CHUNK", ONE_EXACTLY, - expect_uint(assign_variable_callback(large_population_influence_penalty_chunk)) + expect_strong_typedef(assign_variable_callback(large_population_influence_penalty_chunk)) ); } diff --git a/src/openvic-simulation/defines/PopsDefines.hpp b/src/openvic-simulation/defines/PopsDefines.hpp index 0ed9cfbac..fa31f04b9 100644 --- a/src/openvic-simulation/defines/PopsDefines.hpp +++ b/src/openvic-simulation/defines/PopsDefines.hpp @@ -2,7 +2,7 @@ #include "openvic-simulation/dataloader/NodeTools.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSum.hpp" #include "openvic-simulation/types/ProvinceLifeRating.hpp" #include "openvic-simulation/utility/Getters.hpp" @@ -71,8 +71,8 @@ namespace OpenVic { fixed_point_t PROPERTY(nationalist_movement_mil_cap); fixed_point_t PROPERTY(movement_support_uh_factor); fixed_point_t PROPERTY(rebel_occupation_strength_bonus); - pop_size_t PROPERTY(large_population_limit, 0); - pop_size_t PROPERTY(large_population_influence_penalty_chunk, 0); + pop_sum_t PROPERTY(large_population_limit, 0); + pop_sum_t PROPERTY(large_population_influence_penalty_chunk, 0); PopsDefines(); diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp index 9c54d7dea..7cf8d1a87 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.cpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.cpp @@ -72,8 +72,8 @@ void ArtisanalProducer::artisan_tick_handler::calculate_inputs( //throughput scalar, the minimum of stockpile / base_desired_quantity //inputs_bought_fraction uses base_desired_quantity as population size is cancelled in the production and input calculations. const pop_size_t pop_size = pop.get_size(); - fixed_point_t inputs_bought_numerator = pop_size, - inputs_bought_denominator = production_type.base_workforce_size, + fixed_point_t inputs_bought_numerator = type_safe::get(pop_size), + inputs_bought_denominator = type_safe::get(production_type.base_workforce_size), inputs_bought_fraction_v = inputs_bought_numerator / inputs_bought_denominator; distinct_goods_to_buy = 0; @@ -494,7 +494,7 @@ fixed_point_t ArtisanalProducer::calculate_production_type_score( k * fixed_point_t::mul_div(costs, costs, revenue) -(1+k)*costs + revenue - ) * Pop::size_denominator / workforce; //factor out pop size without making values too small + ).mul_div(Pop::size_denominator, workforce); //factor out pop size without making values too small } ProductionType const* ArtisanalProducer::pick_production_type( diff --git a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp index 9c0078cf0..fa06298f8 100644 --- a/src/openvic-simulation/economy/production/ArtisanalProducer.hpp +++ b/src/openvic-simulation/economy/production/ArtisanalProducer.hpp @@ -7,7 +7,6 @@ #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/fixed_point/FixedPointMap.hpp" #include "openvic-simulation/types/fixed_point/Fraction.hpp" -#include "openvic-simulation/types/PopSize.hpp" #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { @@ -23,6 +22,7 @@ namespace OpenVic { struct ProductionType; struct ProvinceInstance; struct RandomU32; + struct pop_size_t; struct ArtisanalProducer { private: diff --git a/src/openvic-simulation/economy/production/Employee.hpp b/src/openvic-simulation/economy/production/Employee.hpp index a8d5b73c5..1d6208f6a 100644 --- a/src/openvic-simulation/economy/production/Employee.hpp +++ b/src/openvic-simulation/economy/production/Employee.hpp @@ -1,7 +1,7 @@ #pragma once #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" namespace OpenVic { struct CountryInstance; diff --git a/src/openvic-simulation/economy/production/FactoryProducer.hpp b/src/openvic-simulation/economy/production/FactoryProducer.hpp index 829996f6f..380419e9a 100644 --- a/src/openvic-simulation/economy/production/FactoryProducer.hpp +++ b/src/openvic-simulation/economy/production/FactoryProducer.hpp @@ -5,7 +5,7 @@ #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/fixed_point/FixedPointMap.hpp" #include "openvic-simulation/types/OrderedContainers.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/utility/Getters.hpp" namespace OpenVic { diff --git a/src/openvic-simulation/economy/production/ProductionType.cpp b/src/openvic-simulation/economy/production/ProductionType.cpp index d9bd08e83..732aa70c5 100644 --- a/src/openvic-simulation/economy/production/ProductionType.cpp +++ b/src/openvic-simulation/economy/production/ProductionType.cpp @@ -8,6 +8,7 @@ #include "openvic-simulation/misc/GameRulesManager.hpp" #include "openvic-simulation/population/PopManager.hpp" #include "openvic-simulation/population/PopType.hpp" +#include "openvic-simulation/population/PopSum.hpp" using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -360,7 +361,7 @@ bool ProductionTypeManager::load_production_types_file( "employees", ZERO_OR_ONE, _expect_job_list(good_definition_manager, pop_manager, move_variable_callback(jobs)), "type", ZERO_OR_ONE, expect_identifier(expect_mapped_string(template_type_map, assign_variable_callback(template_type))), - "workforce", ZERO_OR_ONE, expect_uint(assign_variable_callback(base_workforce_size)), + "workforce", ZERO_OR_ONE, expect_strong_typedef(assign_variable_callback(base_workforce_size)), "input_goods", ZERO_OR_ONE, good_definition_manager.expect_good_definition_decimal_map(move_variable_callback(input_goods)), "output_goods", ZERO_OR_ONE, diff --git a/src/openvic-simulation/economy/production/ProductionType.hpp b/src/openvic-simulation/economy/production/ProductionType.hpp index 3a6e2dbf0..67bffaa2b 100644 --- a/src/openvic-simulation/economy/production/ProductionType.hpp +++ b/src/openvic-simulation/economy/production/ProductionType.hpp @@ -6,7 +6,7 @@ #include "openvic-simulation/types/IdentifierRegistry.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/types/PopSprite.hpp" #include "openvic-simulation/utility/Containers.hpp" diff --git a/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp b/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp index f6bfc8062..04435d180 100644 --- a/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp +++ b/src/openvic-simulation/economy/production/ResourceGatheringOperation.cpp @@ -12,11 +12,13 @@ #include "openvic-simulation/population/Pop.hpp" #include "openvic-simulation/population/PopType.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/types/TypedIndices.hpp" #include "openvic-simulation/utility/Logger.hpp" #include "openvic-simulation/utility/Containers.hpp" +#include + using namespace OpenVic; ResourceGatheringOperation::ResourceGatheringOperation( @@ -65,20 +67,28 @@ void ResourceGatheringOperation::initialise_rgo_size_multiplier() { ProductionType const& production_type = *production_type_nullable; std::span jobs = production_type.get_jobs(); - pop_size_t total_worker_count_in_province = 0; //not counting equivalents + pop_sum_t total_worker_count_in_province = 0; //not counting equivalents for (Job const& job : jobs) { total_worker_count_in_province += location.get_population_by_type(*job.get_pop_type()); } const fixed_point_t size_modifier = calculate_size_modifier(); - const fixed_point_t base_workforce_size = production_type.base_workforce_size; + const pop_size_t base_workforce_size = production_type.base_workforce_size; if (size_modifier == 0) { size_multiplier = 0; } else { - size_multiplier = ((total_worker_count_in_province / (size_modifier * base_workforce_size)).ceil() * fixed_point_t::_1_50).floor(); + size_multiplier = ( + ( + fixed_point_t::from_fraction( + total_worker_count_in_province, + base_workforce_size + ) / size_modifier + ).ceil() + * fixed_point_t::_1_50 + ).floor(); } - max_employee_count_cache = (size_modifier * size_multiplier * base_workforce_size).floor(); + max_employee_count_cache = (size_modifier * size_multiplier * base_workforce_size).floor>(); } fixed_point_t ResourceGatheringOperation::calculate_size_modifier() const { @@ -167,7 +177,7 @@ void ResourceGatheringOperation::after_sell(void* actor, SellResult const& sell_ } void ResourceGatheringOperation::hire() { - pop_size_t const& available_worker_count = total_worker_count_in_province_cache; + pop_sum_t const& available_worker_count = total_worker_count_in_province_cache; total_employees_count_cache = 0; total_paid_employees_count_cache = 0; employees.clear(); //TODO implement Victoria 2 hiring logic @@ -187,8 +197,7 @@ void ResourceGatheringOperation::hire() { proportion_to_hire = 1; } else { //hire all pops proportionally - const fixed_point_t max_worker_count_real = max_employee_count_cache, available_worker_count_real = available_worker_count; - proportion_to_hire = max_worker_count_real / available_worker_count_real; + proportion_to_hire = fixed_point_t::from_fraction(max_employee_count_cache, available_worker_count); } std::span jobs = production_type.get_jobs(); @@ -197,7 +206,7 @@ void ResourceGatheringOperation::hire() { for (Job const& job : jobs) { PopType const* const job_pop_type = job.get_pop_type(); if (job_pop_type && *job_pop_type == pop_type) { - const pop_size_t pop_size_to_hire = static_cast((proportion_to_hire * pop.get_size()).floor()); + const pop_size_t pop_size_to_hire = (proportion_to_hire * pop.get_size()).floor>(); if (pop_size_to_hire <= 0) { continue; } @@ -240,16 +249,22 @@ fixed_point_t ResourceGatheringOperation::produce() { } State const& state = *state_ptr; - const pop_size_t state_population = state.get_total_population(); + const pop_sum_t state_population = state.get_total_population(); Job const& owner_job = owner.value(); if (total_owner_count_in_state_cache > 0) { switch (owner_job.get_effect_type()) { case Job::effect_t::OUTPUT: - output_multiplier += owner_job.get_effect_multiplier() * total_owner_count_in_state_cache / state_population; + output_multiplier += owner_job.get_effect_multiplier().mul_div( + total_owner_count_in_state_cache, + state_population + ); break; case Job::effect_t::THROUGHPUT: - throughput_multiplier += owner_job.get_effect_multiplier() * total_owner_count_in_state_cache / state_population; + throughput_multiplier += owner_job.get_effect_multiplier().mul_div( + total_owner_count_in_state_cache, + state_population + ); break; default: spdlog::error_s("Invalid job effect in RGO {}", production_type); @@ -305,18 +320,18 @@ fixed_point_t ResourceGatheringOperation::produce() { } const fixed_point_t effect_multiplier = job.get_effect_multiplier(); - fixed_point_t relative_to_workforce = - fixed_point_t(employees_of_type) / fixed_point_t(max_employee_count_cache); const fixed_point_t amount = job.get_amount(); - if (effect_multiplier != fixed_point_t::_1 && relative_to_workforce > amount) { - relative_to_workforce = amount; - } + const fixed_point_t effect = effect_multiplier != fixed_point_t::_1 + && fixed_point_t::from_fraction(employees_of_type, max_employee_count_cache) > amount + ? effect_multiplier * amount //special Vic2 logic + : effect_multiplier.mul_div(employees_of_type, max_employee_count_cache); + switch (job.get_effect_type()) { case Job::effect_t::OUTPUT: - output_from_workers += effect_multiplier * relative_to_workforce; + output_from_workers += effect; break; case Job::effect_t::THROUGHPUT: - throughput_from_workers += effect_multiplier * relative_to_workforce; + throughput_from_workers += effect; break; default: spdlog::error_s("Invalid job effect in RGO {}", production_type); @@ -380,14 +395,14 @@ void ResourceGatheringOperation::pay_employees(memory::vector& re fixed_point_t::_1 - total_minimum_wage / revenue_left ); const fixed_point_t owner_share = std::min( - fixed_point_t::_2 * total_owner_count_in_state_cache / total_worker_count_in_province_cache, + fixed_point_t::from_fraction(2 * total_owner_count_in_state_cache, total_worker_count_in_province_cache), upper_limit ); for (Pop* owner_pop_ptr : *owner_pops_cache_nullable) { Pop& owner_pop = *owner_pop_ptr; const fixed_point_t income_for_this_pop = std::max( - revenue_left * (owner_share * owner_pop.get_size()) / total_owner_count_in_state_cache, + revenue_left * owner_share.mul_div(owner_pop.get_size(), total_owner_count_in_state_cache), fixed_point_t::epsilon //revenue > 0 is already checked, so rounding up ); owner_pop.add_rgo_owner_income(income_for_this_pop); @@ -425,7 +440,7 @@ void ResourceGatheringOperation::pay_employees(memory::vector& re const pop_size_t employee_size = employee.get_size(); const fixed_point_t income_for_this_pop = std::max( - revenue_left * employee_size / count_workers_to_be_paid, + revenue_left.mul_div(employee_size, count_workers_to_be_paid), fixed_point_t::epsilon //revenue > 0 is already checked, so rounding up ); diff --git a/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp b/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp index d4ea3a3b3..9cbf97b82 100644 --- a/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp +++ b/src/openvic-simulation/economy/production/ResourceGatheringOperation.hpp @@ -3,7 +3,8 @@ #include "openvic-simulation/economy/production/Employee.hpp" #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" +#include "openvic-simulation/population/PopSum.hpp" #include "openvic-simulation/utility/Getters.hpp" #include "openvic-simulation/utility/Containers.hpp" @@ -22,8 +23,8 @@ namespace OpenVic { MarketInstance& market_instance; ModifierEffectCache const& modifier_effect_cache; ProvinceInstance* location_ptr = nullptr; - pop_size_t total_owner_count_in_state_cache = 0; - pop_size_t total_worker_count_in_province_cache = 0; + pop_sum_t total_owner_count_in_state_cache = 0; + pop_sum_t total_worker_count_in_province_cache = 0; memory::vector const* owner_pops_cache_nullable = nullptr; ProductionType const* PROPERTY_RW(production_type_nullable); diff --git a/src/openvic-simulation/map/MapInstance.cpp b/src/openvic-simulation/map/MapInstance.cpp index 8b3e416ec..f1abea6c2 100644 --- a/src/openvic-simulation/map/MapInstance.cpp +++ b/src/openvic-simulation/map/MapInstance.cpp @@ -148,7 +148,7 @@ void MapInstance::update_gamestate(InstanceManager const& instance_manager) { province.update_gamestate(instance_manager); // Update population stats - const pop_size_t province_population = province.get_total_population(); + const pop_sum_t province_population = province.get_total_population(); if (highest_province_population < province_population) { highest_province_population = province_population; } diff --git a/src/openvic-simulation/map/MapInstance.hpp b/src/openvic-simulation/map/MapInstance.hpp index af29a0c2d..693dcc13a 100644 --- a/src/openvic-simulation/map/MapInstance.hpp +++ b/src/openvic-simulation/map/MapInstance.hpp @@ -29,8 +29,8 @@ namespace OpenVic { OV_IFLATMAP_PROPERTY(ProvinceDefinition, ProvinceInstance, province_instance_by_definition); - pop_size_t PROPERTY(highest_province_population, 0); - pop_size_t PROPERTY(total_map_population, 0); + pop_sum_t PROPERTY(highest_province_population, 0); + pop_sum_t PROPERTY(total_map_population, 0); StateManager PROPERTY_REF(state_manager); // TODO - should this be a vector of bools which we resize to the largest enabled canal index? diff --git a/src/openvic-simulation/map/Mapmode.cpp b/src/openvic-simulation/map/Mapmode.cpp index 85ea5a7cf..12f1fae40 100644 --- a/src/openvic-simulation/map/Mapmode.cpp +++ b/src/openvic-simulation/map/Mapmode.cpp @@ -14,7 +14,6 @@ #include "openvic-simulation/population/Culture.hpp" #include "openvic-simulation/population/Religion.hpp" #include "openvic-simulation/types/OrderedContainersMath.hpp" -#include "openvic-simulation/types/PopSize.hpp" using namespace OpenVic; using namespace OpenVic::colour_literals; @@ -266,7 +265,7 @@ bool MapmodeManager::setup_mapmodes(MapDefinition const& map_definition) { // by the same country, relative to the most populous province in that set of provinces if (!province.province_definition.is_water()) { const colour_argb_t::value_type val = colour_argb_t::colour_traits::component_from_fraction( - province.get_total_population(), map_instance.get_highest_province_population() + 1, 0.1f, 1.0f + type_safe::get(province.get_total_population()), type_safe::get(map_instance.get_highest_province_population()) + 1, 0.1f, 1.0f ); return colour_argb_t { 0, val, 0, ALPHA_VALUE }; } else { @@ -275,7 +274,7 @@ bool MapmodeManager::setup_mapmodes(MapDefinition const& map_definition) { }, "MAPMODE_12" ); - ret &= add_mapmode("mapmode_culture", shaded_mapmode(&ProvinceInstance::get_population_by_culture), "MAPMODE_13"); + ret &= add_mapmode("mapmode_culture", shaded_mapmode(&ProvinceInstance::get_population_by_culture), "MAPMODE_13"); ret &= add_mapmode("mapmode_sphere", Mapmode::ERROR_MAPMODE.get_colour_func(), "MAPMODE_14"); ret &= add_mapmode("mapmode_supply", Mapmode::ERROR_MAPMODE.get_colour_func(), "MAPMODE_15"); ret &= add_mapmode("mapmode_party_loyalty", Mapmode::ERROR_MAPMODE.get_colour_func(), "MAPMODE_16"); @@ -330,7 +329,7 @@ bool MapmodeManager::setup_mapmodes(MapDefinition const& map_definition) { return colour_argb_t::fill_as(f).with_alpha(ALPHA_VALUE); } ); - ret &= add_mapmode("mapmode_religion", shaded_mapmode(&ProvinceInstance::get_population_by_religion)); + ret &= add_mapmode("mapmode_religion", shaded_mapmode(&ProvinceInstance::get_population_by_religion)); ret &= add_mapmode("mapmode_terrain_type", get_colour_mapmode(&ProvinceInstance::get_terrain_type)); ret &= add_mapmode( "mapmode_adjacencies", diff --git a/src/openvic-simulation/population/Pop.cpp b/src/openvic-simulation/population/Pop.cpp index bca75d6af..6585b211b 100644 --- a/src/openvic-simulation/population/Pop.cpp +++ b/src/openvic-simulation/population/Pop.cpp @@ -73,7 +73,7 @@ fixed_point_t Pop::get_unemployment_fraction() const { if (!type->can_be_unemployed) { return 0; } - return fixed_point_t(get_unemployed()) / size; + return fixed_point_t::from_fraction(get_unemployed(), size); } void Pop::setup_pop_test_values(IssueManager const& issue_manager) { @@ -120,7 +120,7 @@ void Pop::setup_pop_test_values(IssueManager const& issue_manager) { for (Ideology const& ideology : supporter_equivalents_by_ideology.get_keys()) { test_weight_indexed(supporter_equivalents_by_ideology, ideology, 1, 5); } - supporter_equivalents_by_ideology.rescale(size); + supporter_equivalents_by_ideology.rescale(type_safe::get(size)); supporter_equivalents_by_issue.clear(); for (BaseIssue const& issue : issue_manager.get_party_policies()) { @@ -131,14 +131,14 @@ void Pop::setup_pop_test_values(IssueManager const& issue_manager) { test_weight_ordered(supporter_equivalents_by_issue, reform, 3, 6); } } - rescale_fixed_point_map(supporter_equivalents_by_issue, size); + rescale_fixed_point_map(supporter_equivalents_by_issue, type_safe::get(size)); if (!vote_equivalents_by_party.empty()) { for (auto& [party, value] : vote_equivalents_by_party) { vote_equivalents_by_party[party] = 0; test_weight_ordered(vote_equivalents_by_party, party, 4, 10); } - rescale_fixed_point_map(vote_equivalents_by_party, size); + rescale_fixed_point_map(vote_equivalents_by_party, type_safe::get(size)); } /* Returns a fixed point between 0 and max. */ @@ -255,9 +255,10 @@ void Pop::update_gamestate( ) { max_supported_regiments = 0; } else { - max_supported_regiments = (fixed_point_t(size) / ( - fixed_point_t(military_defines.get_pop_size_per_regiment()) * pop_size_per_regiment_multiplier - )).floor() + 1; + max_supported_regiments = ( + type_safe::get(size) + / (type_safe::get(military_defines.get_pop_size_per_regiment()) * pop_size_per_regiment_multiplier) + ).floor() + 1; } } } diff --git a/src/openvic-simulation/population/Pop.hpp b/src/openvic-simulation/population/Pop.hpp index be0565a37..a39c2b45a 100644 --- a/src/openvic-simulation/population/Pop.hpp +++ b/src/openvic-simulation/population/Pop.hpp @@ -6,11 +6,13 @@ #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/fixed_point/FixedPointMap.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/types/UnitBranchType.hpp" #include "openvic-simulation/utility/Containers.hpp" #include "openvic-simulation/core/portable/ForwardableSpan.hpp" +#include + namespace OpenVic { struct BaseIssue; struct BuyResult; @@ -83,7 +85,7 @@ namespace OpenVic { return static_cast(allowed) <= static_cast(status); } - static constexpr pop_size_t MAX_SIZE = std::numeric_limits::max(); + static constexpr pop_size_t MAX_SIZE = std::numeric_limits>::max(); private: MarketInstance& market_instance; diff --git a/src/openvic-simulation/population/PopManager.cpp b/src/openvic-simulation/population/PopManager.cpp index 6b5b0f45b..3a54d3ff2 100644 --- a/src/openvic-simulation/population/PopManager.cpp +++ b/src/openvic-simulation/population/PopManager.cpp @@ -11,6 +11,9 @@ #include "openvic-simulation/modifier/ModifierManager.hpp" #include "openvic-simulation/core/FormatValidate.hpp" #include "openvic-simulation/utility/Logger.hpp" +#include "openvic-simulation/population/PopSize.hpp" + +#include using namespace OpenVic; using namespace OpenVic::NodeTools; @@ -279,8 +282,8 @@ bool PopManager::load_pop_type_file( "sprite", ONE_EXACTLY, expect_uint(assign_variable_callback(sprite)), "color", ONE_EXACTLY, expect_colour(assign_variable_callback(colour)), "is_artisan", ZERO_OR_ONE, expect_bool(assign_variable_callback(is_artisan)), - "max_size", ZERO_OR_ONE, expect_uint(assign_variable_callback(max_size)), - "merge_max_size", ZERO_OR_ONE, expect_uint(assign_variable_callback(merge_max_size)), + "max_size", ZERO_OR_ONE, expect_strong_typedef(assign_variable_callback(max_size)), + "merge_max_size", ZERO_OR_ONE, expect_strong_typedef(assign_variable_callback(merge_max_size)), "strata", ONE_EXACTLY, stratas.expect_item_identifier(assign_variable_callback_pointer(strata)), "state_capital_only", ZERO_OR_ONE, expect_bool(assign_variable_callback(state_capital_only)), "research_points", ZERO_OR_ONE, expect_fixed_point(assign_variable_callback(research_points)), @@ -472,7 +475,7 @@ bool PopManager::load_pop_bases_into_vector( return true; } - if (culture != nullptr && religion != nullptr && size >= 1 && size <= std::numeric_limits::max()) { + if (culture != nullptr && religion != nullptr && size >= 1 && size <= std::numeric_limits>::max()) { vec.emplace_back(PopBase { type, *culture, *religion, size.floor(), militancy, consciousness, rebel_type }); } else { spdlog::warn_s( diff --git a/src/openvic-simulation/population/PopSize.hpp b/src/openvic-simulation/population/PopSize.hpp new file mode 100644 index 000000000..c1245842f --- /dev/null +++ b/src/openvic-simulation/population/PopSize.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include +#include + +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +#include + +namespace OpenVic { + struct pop_size_t : type_safe::strong_typedef, + type_safe::strong_typedef_op::equality_comparison, + type_safe::strong_typedef_op::relational_comparison, + type_safe::strong_typedef_op::integer_arithmetic, + type_safe::strong_typedef_op::mixed_relational_comparison, + type_safe::strong_typedef_op::mixed_equality_comparison { + using strong_typedef::strong_typedef; + constexpr pop_size_t(std::same_as auto value) : strong_typedef(value) {} + + using ov_return_by_value = void; + + friend inline constexpr fixed_point_t operator*(const pop_size_t lhs, const std::same_as auto rhs) { + return type_safe::get(lhs) * rhs; + } + + friend inline constexpr fixed_point_t operator*(const std::same_as auto lhs, const pop_size_t rhs) { + return lhs * type_safe::get(rhs); + } + + friend inline constexpr fixed_point_t& operator*=(std::same_as auto lhs, const pop_size_t rhs) { + lhs *= type_safe::get(rhs); + return lhs; + } + + friend inline constexpr fixed_point_t operator/(const pop_size_t lhs, const std::same_as auto rhs) { + return type_safe::get(lhs) / rhs; + } + + friend inline constexpr fixed_point_t operator/(const std::same_as auto lhs, const pop_size_t rhs) { + return lhs / type_safe::get(rhs); + } + + friend inline constexpr fixed_point_t& operator/=(std::same_as auto lhs, const pop_size_t rhs) { + lhs /= type_safe::get(rhs); + return lhs; + } + + friend inline constexpr bool operator==(const std::same_as auto lhs, const pop_size_t rhs) { + return lhs == type_safe::get(rhs); + } + + friend inline constexpr auto operator<=>(const std::same_as auto lhs, const pop_size_t rhs) { + return lhs <=> type_safe::get(rhs); + } + + friend inline constexpr auto operator<=>(const pop_size_t lhs, const pop_size_t rhs) { + return type_safe::get(lhs) <=> type_safe::get(rhs); + } + }; +} + +template<> +struct fmt::formatter : fmt::formatter { + fmt::format_context::iterator format(OpenVic::pop_size_t value, fmt::format_context& ctx) const { + return fmt::formatter::format(type_safe::get(value), ctx); + } +}; diff --git a/src/openvic-simulation/population/PopSum.hpp b/src/openvic-simulation/population/PopSum.hpp new file mode 100644 index 000000000..9a5f9d773 --- /dev/null +++ b/src/openvic-simulation/population/PopSum.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include +#include + +#include +#include + +#include "openvic-simulation/population/PopSize.hpp" +#include "openvic-simulation/types/fixed_point/FixedPoint.hpp" + +#include + +namespace OpenVic { + namespace detail { + template + struct basic_pop_sum_t : type_safe::strong_typedef, T>, + type_safe::strong_typedef_op::equality_comparison, + type_safe::strong_typedef_op::relational_comparison, + type_safe::strong_typedef_op::integer_arithmetic, + type_safe::strong_typedef_op::mixed_relational_comparison>, + type_safe::strong_typedef_op::mixed_equality_comparison, + type_safe::strong_typedef_op::mixed_equality_comparison { + using type_safe::strong_typedef, T>::strong_typedef; + + constexpr basic_pop_sum_t(std::same_as auto value) + : type_safe::strong_typedef, T>(value) {} + + constexpr basic_pop_sum_t(pop_size_t value) + : type_safe::strong_typedef, T>(type_safe::get(value)) {} + + using ov_return_by_value = void; + + friend inline constexpr fixed_point_t operator*( // + const SelfT lhs, const std::same_as auto rhs + ) { + return type_safe::get(lhs) * rhs; + } + + friend inline constexpr fixed_point_t operator*( // + const std::same_as auto lhs, const SelfT rhs + ) { + return lhs * type_safe::get(rhs); + } + + friend inline constexpr fixed_point_t& operator*=(std::same_as auto lhs, const SelfT rhs) { + lhs *= type_safe::get(rhs); + return lhs; + } + + friend inline constexpr fixed_point_t operator/( // + const SelfT lhs, const std::same_as auto rhs + ) { + return type_safe::get(lhs) / rhs; + } + + friend inline constexpr fixed_point_t operator/( // + const std::same_as auto lhs, const SelfT rhs + ) { + return lhs / type_safe::get(rhs); + } + + friend inline constexpr fixed_point_t& operator/=(std::same_as auto& lhs, const SelfT rhs) { + lhs /= type_safe::get(rhs); + return lhs; + } + + friend inline constexpr bool operator==(const std::same_as auto lhs, const SelfT rhs) { + return lhs == type_safe::get(rhs); + } + + friend inline constexpr bool operator==(const std::same_as auto lhs, const SelfT rhs) { + return lhs == type_safe::get(rhs); + } + + friend inline constexpr auto operator<=>(const std::same_as auto lhs, const SelfT rhs) { + return lhs <=> type_safe::get(rhs); + } + + friend inline constexpr auto operator<=>(const SelfT lhs, const SelfT rhs) { + return type_safe::get(lhs) <=> type_safe::get(rhs); + } + + friend inline constexpr auto operator<=>(const SelfT lhs, const pop_size_t rhs) { + return type_safe::get(lhs) <=> type_safe::get(rhs); + } + }; + } + + struct pop_sum_t : detail::basic_pop_sum_t { + using basic_pop_sum_t::basic_pop_sum_t; + }; + + struct upop_sum_t : detail::basic_pop_sum_t { + using basic_pop_sum_t::basic_pop_sum_t; + }; +} + +template<> +struct fmt::formatter : fmt::formatter { + fmt::format_context::iterator format(OpenVic::pop_sum_t value, fmt::format_context& ctx) const { + return fmt::formatter::format(type_safe::get(value), ctx); + } +}; + +template<> +struct fmt::formatter : fmt::formatter { + fmt::format_context::iterator format(OpenVic::upop_sum_t value, fmt::format_context& ctx) const { + return fmt::formatter::format(type_safe::get(value), ctx); + } +}; diff --git a/src/openvic-simulation/population/PopType.hpp b/src/openvic-simulation/population/PopType.hpp index a2a5c79b6..dd9073c20 100644 --- a/src/openvic-simulation/population/PopType.hpp +++ b/src/openvic-simulation/population/PopType.hpp @@ -6,7 +6,7 @@ #include "openvic-simulation/types/HasIdentifier.hpp" #include "openvic-simulation/types/HasIndex.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSize.hpp" #include "openvic-simulation/types/PopSprite.hpp" #include "openvic-simulation/types/TypedIndices.hpp" #include "openvic-simulation/utility/Containers.hpp" diff --git a/src/openvic-simulation/population/PopsAggregate.cpp b/src/openvic-simulation/population/PopsAggregate.cpp index f6ca3e621..c8b6bbb21 100644 --- a/src/openvic-simulation/population/PopsAggregate.cpp +++ b/src/openvic-simulation/population/PopsAggregate.cpp @@ -1,4 +1,5 @@ #include "PopsAggregate.hpp" +#include #include "openvic-simulation/country/CountryDefinition.hpp" #include "openvic-simulation/country/CountryInstance.hpp" @@ -8,12 +9,18 @@ #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/types/OrderedContainersMath.hpp" +#include + using namespace OpenVic; PopsAggregate::PopsAggregate( decltype(population_by_strata)::keys_span_type strata_keys, decltype(population_by_type)::keys_span_type pop_type_keys, decltype(supporter_equivalents_by_ideology)::keys_span_type ideology_keys ) : population_by_strata { strata_keys }, + militancy_by_strata_running_total_raw { strata_keys }, + life_needs_fulfilled_by_strata_running_total_raw { strata_keys }, + everyday_needs_fulfilled_by_strata_running_total_raw { strata_keys }, + luxury_needs_fulfilled_by_strata_running_total_raw { strata_keys }, militancy_by_strata { strata_keys }, life_needs_fulfilled_by_strata { strata_keys }, everyday_needs_fulfilled_by_strata { strata_keys }, @@ -22,10 +29,10 @@ PopsAggregate::PopsAggregate( unemployed_pops_by_type { pop_type_keys }, supporter_equivalents_by_ideology { ideology_keys } {} -pop_size_t PopsAggregate::get_population_by_type(PopType const& pop_type) const { +pop_sum_t PopsAggregate::get_population_by_type(PopType const& pop_type) const { return population_by_type.at(pop_type); } -pop_size_t PopsAggregate::get_unemployed_pops_by_type(PopType const& pop_type) const { +pop_sum_t PopsAggregate::get_unemployed_pops_by_type(PopType const& pop_type) const { return unemployed_pops_by_type.at(pop_type); } fixed_point_t PopsAggregate::get_supporter_equivalents_by_ideology(Ideology const& ideology) const { @@ -47,7 +54,7 @@ fixed_point_t PopsAggregate::get_vote_equivalents_by_party(CountryParty const& p } return it.value(); } -fixed_point_t PopsAggregate::get_population_by_culture(Culture const& culture) const { +pop_sum_t PopsAggregate::get_population_by_culture(Culture const& culture) const { const decltype(population_by_culture)::const_iterator it = population_by_culture.find(&culture); if (it != population_by_culture.end()) { @@ -56,7 +63,7 @@ fixed_point_t PopsAggregate::get_population_by_culture(Culture const& culture) c return 0; } } -fixed_point_t PopsAggregate::get_population_by_religion(Religion const& religion) const { +pop_sum_t PopsAggregate::get_population_by_religion(Religion const& religion) const { const decltype(population_by_religion)::const_iterator it = population_by_religion.find(&religion); if (it != population_by_religion.end()) { @@ -65,7 +72,7 @@ fixed_point_t PopsAggregate::get_population_by_religion(Religion const& religion return 0; } } -pop_size_t PopsAggregate::get_population_by_strata(Strata const& strata) const { +pop_sum_t PopsAggregate::get_population_by_strata(Strata const& strata) const { return population_by_strata.at(strata); } fixed_point_t PopsAggregate::get_militancy_by_strata(Strata const& strata) const { @@ -86,15 +93,22 @@ void PopsAggregate::clear_pops_aggregate() { max_supported_regiment_count = 0; _yesterdays_import_value_running_total = fixed_point_t::_0; + literacy_running_total_raw = 0; + consciousness_running_total_raw = 0; + militancy_running_total_raw = 0; average_literacy = fixed_point_t::_0; average_consciousness = fixed_point_t::_0; average_militancy = fixed_point_t::_0; - population_by_strata.fill(0); + militancy_by_strata_running_total_raw.fill(0); + life_needs_fulfilled_by_strata_running_total_raw.fill(0); + everyday_needs_fulfilled_by_strata_running_total_raw.fill(0); + luxury_needs_fulfilled_by_strata_running_total_raw.fill(0); militancy_by_strata.fill(fixed_point_t::_0); life_needs_fulfilled_by_strata.fill(fixed_point_t::_0); everyday_needs_fulfilled_by_strata.fill(fixed_point_t::_0); luxury_needs_fulfilled_by_strata.fill(fixed_point_t::_0); + population_by_strata.fill(0); population_by_type.fill(0); unemployed_pops_by_type.fill(0); @@ -110,23 +124,27 @@ void PopsAggregate::add_pops_aggregate(PopsAggregate& part) { max_supported_regiment_count += part.get_max_supported_regiment_count(); _yesterdays_import_value_running_total += part.get_yesterdays_import_value_untracked(); - // TODO - change casting if pop_size_t changes type - const fixed_point_t part_population = fixed_point_t(part.get_total_population()); - average_literacy += part.get_average_literacy() * part_population; - average_consciousness += part.get_average_consciousness() * part_population; - average_militancy += part.get_average_militancy() * part_population; - - population_by_strata += part.get_population_by_strata(); - militancy_by_strata.mul_add(part.get_militancy_by_strata(), part.get_population_by_strata()); - life_needs_fulfilled_by_strata.mul_add( - part.get_life_needs_fulfilled_by_strata(), part.get_population_by_strata() - ); - everyday_needs_fulfilled_by_strata.mul_add( - part.get_everyday_needs_fulfilled_by_strata(), part.get_population_by_strata() - ); - luxury_needs_fulfilled_by_strata.mul_add( - part.get_luxury_needs_fulfilled_by_strata(), part.get_population_by_strata() - ); + const pop_sum_t part_population = part.get_total_population(); + const int64_t part_population_v = type_safe::get(part_population); + literacy_running_total_raw += part_population_v + * static_cast(part.get_average_literacy().get_raw_value()); + consciousness_running_total_raw += part_population_v + * static_cast(part.get_average_consciousness().get_raw_value()); + militancy_running_total_raw += part_population_v + * static_cast(part.get_average_militancy().get_raw_value()); + + for (auto const& [strata, strata_population] : part.get_population_by_strata()) { + population_by_strata.at(strata) += strata_population; + const int64_t strata_population_v = type_safe::get(strata_population); + militancy_by_strata_running_total_raw.at(strata) += strata_population_v + * static_cast(part.militancy_by_strata.at(strata).get_raw_value()); + life_needs_fulfilled_by_strata_running_total_raw.at(strata) += strata_population_v + * static_cast(part.life_needs_fulfilled_by_strata.at(strata).get_raw_value()); + everyday_needs_fulfilled_by_strata_running_total_raw.at(strata) += strata_population_v + * static_cast(part.everyday_needs_fulfilled_by_strata.at(strata).get_raw_value()); + luxury_needs_fulfilled_by_strata_running_total_raw.at(strata) += strata_population_v + * static_cast(part.luxury_needs_fulfilled_by_strata.at(strata).get_raw_value()); + } population_by_type += part.get_population_by_type(); unemployed_pops_by_type += part.get_unemployed_pops_by_type(); @@ -142,18 +160,26 @@ void PopsAggregate::add_pops_aggregate(Pop const& pop) { total_population += pop_size; yesterdays_import_value += pop.get_yesterdays_import_value().get_copy_of_value(); - average_literacy += pop.get_literacy() * pop_size; - average_consciousness += pop.get_consciousness() * pop_size; - average_militancy += pop.get_militancy() * pop_size; + const int64_t pop_size_v = type_safe::get(pop_size); + literacy_running_total_raw += pop_size_v + * static_cast(pop.get_literacy().get_raw_value()); + consciousness_running_total_raw += pop_size_v + * static_cast(pop.get_consciousness().get_raw_value()); + militancy_running_total_raw += pop_size_v + * static_cast(pop.get_militancy().get_raw_value()); PopType const& pop_type = *pop.get_type(); Strata const& strata = pop_type.strata; population_by_strata.at(strata) += pop_size; - militancy_by_strata.at(strata) += pop.get_militancy() * pop_size; - life_needs_fulfilled_by_strata.at(strata) += pop.get_life_needs_fulfilled() * pop_size; - everyday_needs_fulfilled_by_strata.at(strata) += pop.get_everyday_needs_fulfilled() * pop_size; - luxury_needs_fulfilled_by_strata.at(strata) += pop.get_luxury_needs_fulfilled() * pop_size; + militancy_by_strata_running_total_raw.at(strata) += pop_size_v + * static_cast(pop.get_militancy().get_raw_value()); + life_needs_fulfilled_by_strata_running_total_raw.at(strata) += pop_size_v + * static_cast(pop.get_life_needs_fulfilled().get_raw_value()); + everyday_needs_fulfilled_by_strata_running_total_raw.at(strata) += pop_size_v + * static_cast(pop.get_everyday_needs_fulfilled().get_raw_value()); + luxury_needs_fulfilled_by_strata_running_total_raw.at(strata) += pop_size_v + * static_cast(pop.get_luxury_needs_fulfilled().get_raw_value()); population_by_type.at(pop_type) += pop_size; unemployed_pops_by_type.at(pop_type) += pop.get_unemployed(); @@ -170,32 +196,26 @@ void PopsAggregate::add_pops_aggregate(Pop const& pop) { void PopsAggregate::normalise_pops_aggregate() { if (total_population > 0) { - average_literacy /= total_population; - average_consciousness /= total_population; - average_militancy /= total_population; - - static const fu2::function handle_div_by_zero = []( - fixed_point_t& lhs, - pop_size_t const& rhs - )->void { - lhs = fixed_point_t::_0; - }; - militancy_by_strata.divide_assign_handle_zero( - population_by_strata, - handle_div_by_zero - ); - life_needs_fulfilled_by_strata.divide_assign_handle_zero( - population_by_strata, - handle_div_by_zero - ); - everyday_needs_fulfilled_by_strata.divide_assign_handle_zero( - population_by_strata, - handle_div_by_zero - ); - luxury_needs_fulfilled_by_strata.divide_assign_handle_zero( - population_by_strata, - handle_div_by_zero - ); + const int64_t total_population_v = type_safe::get(total_population); + average_literacy = fixed_point_t::parse_raw(static_cast(literacy_running_total_raw / total_population_v)); + average_consciousness = fixed_point_t::parse_raw(static_cast(consciousness_running_total_raw / total_population_v)); + average_militancy = fixed_point_t::parse_raw(static_cast(militancy_running_total_raw / total_population_v)); + + for (auto const& [strata, strata_population] : population_by_strata) { + const int64_t strata_population_v = type_safe::get(strata_population); + militancy_by_strata.at(strata) = fixed_point_t::parse_raw(static_cast( + militancy_by_strata_running_total_raw.at(strata) / strata_population_v + )); + life_needs_fulfilled_by_strata.at(strata) = fixed_point_t::parse_raw(static_cast( + life_needs_fulfilled_by_strata_running_total_raw.at(strata) / strata_population_v + )); + everyday_needs_fulfilled_by_strata.at(strata) = fixed_point_t::parse_raw(static_cast( + everyday_needs_fulfilled_by_strata_running_total_raw.at(strata) / strata_population_v + )); + luxury_needs_fulfilled_by_strata.at(strata) = fixed_point_t::parse_raw(static_cast( + luxury_needs_fulfilled_by_strata_running_total_raw.at(strata) / strata_population_v + )); + } } } diff --git a/src/openvic-simulation/population/PopsAggregate.hpp b/src/openvic-simulation/population/PopsAggregate.hpp index 65f9af0a8..5a687aa55 100644 --- a/src/openvic-simulation/population/PopsAggregate.hpp +++ b/src/openvic-simulation/population/PopsAggregate.hpp @@ -4,9 +4,10 @@ #include "openvic-simulation/types/fixed_point/FixedPointMap.hpp" #include "openvic-simulation/types/IndexedFlatMap.hpp" #include "openvic-simulation/types/OrderedContainers.hpp" -#include "openvic-simulation/types/PopSize.hpp" +#include "openvic-simulation/population/PopSum.hpp" #include "openvic-simulation/utility/Getters.hpp" #include "openvic-simulation/utility/reactive/MutableState.hpp" +#include "boost/int128/detail/int128_imp.hpp" namespace OpenVic { struct BaseIssue; @@ -22,29 +23,36 @@ namespace OpenVic { struct PopsAggregate { private: - pop_size_t PROPERTY(total_population, 0); + pop_sum_t PROPERTY(total_population, 0); size_t PROPERTY(max_supported_regiment_count, 0); fixed_point_t _yesterdays_import_value_running_total; OV_STATE_PROPERTY(fixed_point_t, yesterdays_import_value); //>= 0 // TODO - population change (growth + migration), monthly totals + breakdown by source/destination + boost::int128::int128_t literacy_running_total_raw; + boost::int128::int128_t consciousness_running_total_raw; + boost::int128::int128_t militancy_running_total_raw; fixed_point_t PROPERTY(average_literacy); fixed_point_t PROPERTY(average_consciousness); fixed_point_t PROPERTY(average_militancy); - OV_IFLATMAP_PROPERTY(Strata, pop_size_t, population_by_strata); + IndexedFlatMap militancy_by_strata_running_total_raw; + IndexedFlatMap life_needs_fulfilled_by_strata_running_total_raw; + IndexedFlatMap everyday_needs_fulfilled_by_strata_running_total_raw; + IndexedFlatMap luxury_needs_fulfilled_by_strata_running_total_raw; OV_IFLATMAP_PROPERTY(Strata, fixed_point_t, militancy_by_strata); OV_IFLATMAP_PROPERTY(Strata, fixed_point_t, life_needs_fulfilled_by_strata); OV_IFLATMAP_PROPERTY(Strata, fixed_point_t, everyday_needs_fulfilled_by_strata); OV_IFLATMAP_PROPERTY(Strata, fixed_point_t, luxury_needs_fulfilled_by_strata); + OV_IFLATMAP_PROPERTY(Strata, pop_sum_t, population_by_strata); - OV_IFLATMAP_PROPERTY(PopType, pop_size_t, population_by_type); - OV_IFLATMAP_PROPERTY(PopType, pop_size_t, unemployed_pops_by_type); + OV_IFLATMAP_PROPERTY(PopType, pop_sum_t, population_by_type); + OV_IFLATMAP_PROPERTY(PopType, pop_sum_t, unemployed_pops_by_type); OV_IFLATMAP_PROPERTY(Ideology, fixed_point_t, supporter_equivalents_by_ideology); fixed_point_map_t PROPERTY(supporter_equivalents_by_issue); fixed_point_map_t PROPERTY(vote_equivalents_by_party); - ordered_map PROPERTY(population_by_culture); - ordered_map PROPERTY(population_by_religion); + ordered_map PROPERTY(population_by_culture); + ordered_map PROPERTY(population_by_religion); protected: PopsAggregate( @@ -65,7 +73,7 @@ namespace OpenVic { fixed_point_t get_supporter_equivalents_by_issue(Ideology const& ideology) const; fixed_point_t get_supporter_equivalents_by_issue(BaseIssue const& issue) const; fixed_point_t get_vote_equivalents_by_party(CountryParty const& party) const; - fixed_point_t get_population_by_culture(Culture const& culture) const; - fixed_point_t get_population_by_religion(Religion const& religion) const; + pop_sum_t get_population_by_culture(Culture const& culture) const; + pop_sum_t get_population_by_religion(Religion const& religion) const; }; } \ No newline at end of file diff --git a/src/openvic-simulation/types/IndexedFlatMap.hpp b/src/openvic-simulation/types/IndexedFlatMap.hpp index 1ea92be65..fcbfe37a6 100644 --- a/src/openvic-simulation/types/IndexedFlatMap.hpp +++ b/src/openvic-simulation/types/IndexedFlatMap.hpp @@ -851,7 +851,7 @@ namespace OpenVic { template IndexedFlatMap& operator/=(IndexedFlatMap const& other) - requires divide_assignable { + requires divide_assignable && equalable { static_assert(has_index); if (!check_subset_span_match(other)) { return *this; @@ -873,29 +873,6 @@ namespace OpenVic { return *this; } - template - IndexedFlatMap& divide_assign_handle_zero( - IndexedFlatMap const& other, - fu2::function handle_div_by_zero - ) requires divide_assignable { - static_assert(has_index); - if (!check_subset_span_match(other)) { - return *this; - } - - for (ForwardedKeyType const& key : other.get_keys()) { - if (other.at(key) == static_cast(0)) { - handle_div_by_zero( - this->at(key), - other.at(key) - ); - } else { - this->at(key) /= other.at(key); - } - } - return *this; - } - // Compound assignment addition operator (Map += Scalar) template IndexedFlatMap& operator+=(ScalarType const& scalar) @@ -945,41 +922,6 @@ namespace OpenVic { return *this; } - template - constexpr IndexedFlatMap& mul_add( - IndexedFlatMap const& other, - ScalarType const& factor - ) requires mul_add_assignable { - static_assert(has_index); - for (ForwardedKeyType const& key : get_shared_keys(other)) { - at(key) += other.at(key) * factor; - } - - return *this; - } - - template - constexpr IndexedFlatMap& mul_add( - IndexedFlatMap const& a, - IndexedFlatMap const& b - ) requires mul_add_assignable { - static_assert(has_index); - if (a.get_min_index() != b.get_min_index() || a.get_max_index() != b.get_max_index()) { - spdlog::error_s( - "DEVELOPER: OpenVic::IndexedFlatMap<{},{}> attempted mul_add where a and b don't have the same keys. This is not implemented.", - type_name(), - type_name() - ); - assert(a.get_min_index() == b.get_min_index() && a.get_max_index() == b.get_max_index()); - } - - for (ForwardedKeyType const& key : get_shared_keys(a)) { - at(key) += a.at(key) * b.at(key); - } - - return *this; - } - template constexpr IndexedFlatMap& rescale(ScalarType const& new_total) requires std::default_initializable diff --git a/src/openvic-simulation/types/PopSize.hpp b/src/openvic-simulation/types/PopSize.hpp deleted file mode 100644 index 6e00eeb04..000000000 --- a/src/openvic-simulation/types/PopSize.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - -namespace OpenVic { - using pop_size_t = int32_t; -} \ No newline at end of file diff --git a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp index 5aebb2711..764c7c4fb 100644 --- a/src/openvic-simulation/types/fixed_point/FixedPoint.hpp +++ b/src/openvic-simulation/types/fixed_point/FixedPoint.hpp @@ -14,6 +14,8 @@ #include #include +#include + #include "openvic-simulation/types/StackString.hpp" #include "openvic-simulation/utility/Getters.hpp" #include "openvic-simulation/utility/Logger.hpp" @@ -218,6 +220,14 @@ namespace OpenVic { OV_SPEED_INLINE constexpr T truncate() const { return static_cast(*this); } + + template + OV_SPEED_INLINE static constexpr StrongType multiply_truncate(StrongType const& integer, fixed_point_t const& fp) { + return StrongType(multiply_truncate( + type_safe::get(integer), + fp + )); + } template OV_SPEED_INLINE static constexpr T multiply_truncate(T const& integer, fixed_point_t const& fp) { @@ -424,7 +434,7 @@ namespace OpenVic { if (!std::is_constant_evaluated()) { spdlog::warn_s("parse_capped value exceeded int32 max. It still fits but exceeds fixed_point_t::usable_max"); } - result = fixed_point_t::parse_raw(value << fixed_point_t::PRECISION); + result = fixed_point_t::parse_raw(value << PRECISION); } } else { result = fixed_point_t(static_cast(value)); @@ -432,6 +442,27 @@ namespace OpenVic { return result; } + + template + OV_SPEED_INLINE static constexpr fixed_point_t from_fraction(StrongType const& numerator, StrongType const& denominator) { + return from_fraction(type_safe::get(numerator), type_safe::get(denominator)); + } + + template + OV_SPEED_INLINE static constexpr fixed_point_t from_fraction(const TNumerator numerator, const TDenominator denominator) { + return parse_raw((static_cast(numerator) << PRECISION) / static_cast(denominator)); + } + + static constexpr fixed_point_t from_fraction(const int64_t numerator, const int64_t denominator) { + assert(numerator < 140737488355328LL); //2^47 + assert(numerator >= -140737488355328LL); //- 2^47 + return parse_raw((numerator << PRECISION) / denominator); + } + + static constexpr fixed_point_t from_fraction(const uint64_t numerator, const uint64_t denominator) { + assert(numerator < 281474976710656ULL); //2^48 + return parse_raw((numerator << PRECISION) / denominator); + } // Deterministic OV_SPEED_INLINE constexpr std::from_chars_result from_chars(char const* begin, char const* end) { @@ -649,9 +680,19 @@ namespace OpenVic { return *this; } + template + OV_SPEED_INLINE constexpr fixed_point_t mul_div(StrongType const& numerator, StrongType const& denominator) const { + return mul_div(type_safe::get(numerator), type_safe::get(denominator)); + } + + template + OV_SPEED_INLINE constexpr fixed_point_t mul_div(T const& numerator, T const& denominator) const { + return parse_raw(value * numerator / denominator); + } + //Preserves accuracy. Performing a normal multiplication of small values results in 0. OV_SPEED_INLINE constexpr static fixed_point_t mul_div(fixed_point_t const& a, fixed_point_t const& b, fixed_point_t const& denominator) { - return parse_raw(a.value * b.value / denominator.value); + return a.mul_div(b.value, denominator.value); } OV_SPEED_INLINE constexpr friend fixed_point_t operator%(fixed_point_t const& lhs, fixed_point_t const& rhs) { diff --git a/tests/src/core/random/ExtendedMath.hpp b/tests/src/core/random/ExtendedMath.hpp deleted file mode 100644 index 13a9dc213..000000000 --- a/tests/src/core/random/ExtendedMath.hpp +++ /dev/null @@ -1,215 +0,0 @@ -#pragma once - -#include -#include - -namespace OpenVic::testing { - /** @brief Structure to hold a 96-bit signed integer product. - * The product of a signed 64-bit number and an unsigned 32-bit number - * requires up to 96 bits (2^63 * 2^32 = 2^95). - * - * The result is stored as a magnitude across two 64-bit words, plus a sign flag. - * The mathematical value is: (-1)^is_negative * (high * 2^64 + low) - */ - struct Int96Product { - uint64_t low; ///< The lower 64 bits of the product's magnitude. - uint64_t high; ///< The upper 32 bits (stored in a 64-bit word) of the product's magnitude. - bool is_negative; ///< True if the overall product is negative. - }; - - /** @brief Multiplies an int64_t by a uint32_t, returning the exact 96-bit result. - * This function performs the multiplication using standard C++ 64-bit arithmetic, - * preventing intermediate overflow and storing the full result in a 96-bit structure. - * - * @param a The signed 64-bit integer factor (int64_t). - * @param b The unsigned 32-bit integer factor (uint32_t). - * @return An Int96Product structure containing the full 96-bit signed result. - */ - [[nodiscard]] Int96Product portable_int64_mult_uint32_96bit(int64_t a, uint32_t b) { - Int96Product result = {0, 0, false}; - - // 1. Handle zero factors - if (a == 0 || b == 0) { - return result; - } - - // 2. Determine Sign and Magnitude - result.is_negative = (a < 0); - - // Get the magnitude of 'a' as an unsigned 64-bit number. - // Note: If a == INT64_MIN, -a is undefined in signed arithmetic. - // Using (uint64_t)a converts it to 2's complement representation, and - // applying the negative sign to the result of a * 1 (for INT64_MIN) yields the correct magnitude. - uint64_t a_mag; - if (a == std::numeric_limits::min()) { - // Absolute value of INT64_MIN is 2^63, which requires a custom calculation - // to avoid signed overflow/UB when finding the magnitude. - // INT64_MIN is 0x8000000000000000. Its magnitude is 0x8000000000000000. - // The cast to uint64_t yields this magnitude directly. - a_mag = (uint64_t)std::numeric_limits::min(); - } else { - a_mag = (a < 0) ? (uint64_t)-a : (uint64_t)a; - } - - uint64_t b_mag = b; // b is already unsigned and positive - - // 3. Perform 64x32 -> 96-bit multiplication using split-word technique - - // Masks and constants - const uint64_t MASK_32 = 0xFFFFFFFFULL; - - // Split a_mag into 32-bit parts: a_mag = a_H * 2^32 + a_L - uint64_t a_L = a_mag & MASK_32; - uint64_t a_H = a_mag >> 32; - - // Partial product P_L = a_L * b_mag (32x32 -> 64 bits) - uint64_t P_L = a_L * b_mag; - - // Partial product P_H = a_H * b_mag (32x32 -> 64 bits) - uint64_t P_H = a_H * b_mag; - - // 4. Combine Products - - // The low word of the 96-bit result is simply P_L - result.low = P_L; - - // The high word (P_H * 2^32) contribution needs to be added to the carry from P_L. - // P_L_carry is the upper 32 bits of P_L, which carries into the high word. - uint64_t P_L_carry = P_L >> 32; - - // The Middle Word (bits 32-95) is P_H + P_L_carry - // This is the total coefficient for 2^32. - uint64_t middle_word = P_H + P_L_carry; - - // The upper half of the 96-bit result (result.high, bits 64-95) - // is the upper 32 bits of the middle_word. - // This is the correct coefficient for 2^64. - result.high = middle_word >> 32; - - // Note: Since a_H * b_mag is max (2^32-1) * (2^32-1) < 2^64, and P_L_carry < 2^32, - // the sum result.high is safely stored in a uint64_t. The highest bit (2^95) - // of the product will be contained in the result.high word. - - return result; - } - - /** - * @brief Structure to hold the result of the division operation. - */ - struct Int96DivisionResult { - int64_t quotient; ///< The quotient (clamped to INT64_MAX/MIN if overflow occurs). - int64_t remainder; ///< The remainder, with the same sign as the dividend. - bool quotient_overflow; ///< True if the mathematical quotient exceeds int64_t limits. - }; - - /** - * @brief Divides a 96-bit signed integer (Int96Product) by an int64_t, checking for quotient overflow. - * * This is implemented using a portable iterative long division algorithm. - * - * @param dividend The 96-bit signed number. - * @param divisor The signed 64-bit divisor. - * @return An IntDivisionResult containing the quotient, remainder, and overflow status. - */ - Int96DivisionResult portable_int96_div_int64(const Int96Product& dividend, int64_t divisor) { - Int96DivisionResult result = {0, 0, false}; - - // 1. Handle Divisor Zero - if (divisor == 0) { - result.quotient_overflow = true; - result.quotient = std::numeric_limits::max(); // Return clamped value for safety - result.remainder = 0; - return result; - } - - // 2. Handle Dividend Zero - if (dividend.low == 0 && dividend.high == 0) { - return result; - } - - // 3. Determine Signs and Magnitudes - const bool result_negative = (dividend.is_negative != (divisor < 0)); - - uint64_t d_mag; - if (divisor == std::numeric_limits::min()) { - // INT64_MIN magnitude is 2^63 (0x8000000000000000ULL) - d_mag = 1ULL << 63; - } else { - // For other negative numbers, -divisor is safe. For positive, divisor is fine. - d_mag = (divisor < 0) ? (uint64_t)-divisor : (uint64_t)divisor; - } - - // 4. Perform 96-bit / 64-bit Division on Magnitudes (Iterative Long Division) - - uint64_t quotient_mag = 0; - uint64_t remainder_mag = 0; // Remainder is 64-bit and accumulates dividend bits - - // Iterate through all 96 bits of the dividend magnitude (from bit 95 down to 0) - for (int i = 95; i >= 0; --i) { - remainder_mag <<= 1; - - // Bring in the next dividend bit - if (i >= 64) { - // Bit is in dividend.high at position i-64 (Max 32 bits) - if ((dividend.high >> (i - 64)) & 1) { - remainder_mag |= 1; - } - } else { - // Bit is in dividend.low at position i (64 bits) - if ((dividend.low >> i) & 1) { - remainder_mag |= 1; - } - } - - // Try to subtract divisor magnitude from the current remainder - if (remainder_mag >= d_mag) { - remainder_mag -= d_mag; - - if (i >= 64) { - // If a quotient bit is set at or above index 64, the mathematical quotient - // is >= 2^64, which is an overflow for int64_t. - result.quotient_overflow = true; - // We must continue the loop to calculate the correct final remainder. - } else { - // Store the quotient bit $0 \le i \le 63$. - quotient_mag |= (1ULL << i); - } - } - } - - // 5. Final Overflow Check and Sign Application - const uint64_t INT64_MAX_MAG = ~(1ULL << 63); - // The magnitude of INT64_MIN is 2^63, which is 0x8000000000000000ULL - const uint64_t INT64_MIN_MAG = 1ULL << 63; - - if (result_negative) { - // If overflow was detected earlier OR if the 64-bit quotient magnitude exceeds 2^63 (INT64_MIN magnitude) - if (result.quotient_overflow || (quotient_mag > INT64_MIN_MAG)) { - result.quotient_overflow = true; - result.quotient = std::numeric_limits::min(); - } else { - // Check for the exact INT64_MIN case (magnitude 2^63) - if (quotient_mag == INT64_MIN_MAG) { - result.quotient = std::numeric_limits::min(); - } else { - result.quotient = -(int64_t)quotient_mag; - } - } - } else { // Positive result - if (result.quotient_overflow || (quotient_mag > INT64_MAX_MAG)) { - result.quotient_overflow = true; - result.quotient = std::numeric_limits::max(); - } else { - result.quotient = (int64_t)quotient_mag; - } - } - - // Apply sign to remainder (must have the same sign as the dividend). - if (dividend.is_negative && remainder_mag != 0) { - result.remainder = -(int64_t)remainder_mag; - } else { - result.remainder = (int64_t)remainder_mag; - } - - return result; - } -} \ No newline at end of file diff --git a/tests/src/core/random/WeightedSampling.cpp b/tests/src/core/random/WeightedSampling.cpp index 6d2336155..2ccb0d963 100644 --- a/tests/src/core/random/WeightedSampling.cpp +++ b/tests/src/core/random/WeightedSampling.cpp @@ -1,16 +1,14 @@ #include "openvic-simulation/types/fixed_point/FixedPoint.hpp" #include "openvic-simulation/core/random/WeightedSampling.hpp" -#include "core/random/ExtendedMath.hpp" - #include #include +#include #include #include using namespace OpenVic; -using namespace OpenVic::testing; constexpr uint32_t max_random_value = std::numeric_limits().max(); @@ -46,16 +44,12 @@ TEST_CASE("WeightedSampling weights", "[WeightedSampling]") { fixed_point_t cumulative_weight = 0; for (size_t i = 0; i < weights.size(); ++i) { cumulative_weight += weights[i]; - const Int96DivisionResult random_value = portable_int96_div_int64( - portable_int64_mult_uint32_96bit( - cumulative_weight.get_raw_value(), - max_random_value - ), - weights_sum.get_raw_value() - ); - assert(!random_value.quotient_overflow); + const boost::int128::int128_t cumulative_weight_128 = cumulative_weight.get_raw_value(); + const boost::int128::int128_t max_random_value_128 = max_random_value; + const boost::int128::int128_t weights_sum_128 = weights_sum.get_raw_value(); + const boost::int128::int128_t random_value = cumulative_weight_128 * max_random_value_128 / weights_sum_128; CHECK(sample_weighted_index( - static_cast(random_value.quotient), + static_cast(random_value), weights, weights_sum ) == i);