1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Add helper functions for integer division rounding

Added set of functions that perform integer division with different
rounding modes:
- divideAndCeil - rounds up to next integer
- divideAndRound - rounds to nearest integer
- divideAndFloor - rounds to previous integer (equivalent to default
division)

Intended for use in library, where usage of floating point might lead to
desync in multiplayer games.

Replaced some cases that I knew of, including recent handicap PR
This commit is contained in:
Ivan Savenko 2024-08-01 20:48:55 +00:00
parent 257fb8c70c
commit 81e6207df0
5 changed files with 33 additions and 5 deletions

View File

@ -700,6 +700,33 @@ namespace vstd
return a + (b - a) * f;
}
/// Divides dividend by divisor and rounds result up
/// For use with integer-only arithmetic
template<typename Integer1, typename Integer2>
Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
{
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
return (dividend + divisor - 1) / divisor;
}
/// Divides dividend by divisor and rounds result to nearest
/// For use with integer-only arithmetic
template<typename Integer1, typename Integer2>
Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
{
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
return (dividend + divisor / 2 - 1) / divisor;
}
/// Divides dividend by divisor and rounds result down
/// For use with integer-only arithmetic
template<typename Integer1, typename Integer2>
Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)
{
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
return dividend / divisor;
}
template<typename Floating>
bool isAlmostZero(const Floating & value)
{

View File

@ -145,7 +145,8 @@ int DamageCalculator::getActorAttackIgnored() const
if(multAttackReductionPercent > 0)
{
int reduction = (getActorAttackBase() * multAttackReductionPercent + 49) / 100; //using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix)
//using ints so 1.5 for 5 attack is rounded down as in HotA / h3assist etc. (keep in mind h3assist 1.2 shows wrong value for 15 attack points and unupg. nix)
int reduction = vstd::divideAndRound( getActorAttackBase() * multAttackReductionPercent, 100);
return -std::min(reduction, getActorAttackBase());
}
return 0;

View File

@ -227,7 +227,7 @@ TResources CGTownInstance::dailyIncome() const
auto playerSettings = cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner());
for(TResources::nziterator it(ret); it.valid(); it++)
// always round up income - we don't want to always produce zero if handicap in use
ret[it->resType] = (ret[it->resType] * playerSettings.handicap.percentIncome + 99) / 100;
ret[it->resType] = vstd::divideAndCeil(ret[it->resType] * playerSettings.handicap.percentIncome, 100);
return ret;
}
@ -1271,7 +1271,7 @@ int GrowthInfo::totalGrowth() const
ret += entry.count;
// always round up income - we don't want buildings to always produce zero if handicap in use
return (ret * handicapPercentage + 99) / 100;
return vstd::divideAndCeil(ret * handicapPercentage, 100);
}
void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const

View File

@ -200,7 +200,7 @@ ui32 CGMine::getProducedQuantity() const
{
auto * playerSettings = cb->getPlayerSettings(getOwner());
// always round up income - we don't want mines to always produce zero if handicap in use
return (producedQuantity * playerSettings->handicap.percentIncome + 99) / 100;
return vstd::divideAndCeil(producedQuantity * playerSettings->handicap.percentIncome, 100);
}
void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const

View File

@ -1267,7 +1267,7 @@ void BattleActionProcessor::handleDeathStare(const CBattleInfoCallback & battle,
vstd::amin(chanceToKill, 1); //cap at 100%
int killedCreatures = gameHandler->getRandomGenerator().nextBinomialInt(attacker->getCount(), chanceToKill);
int maxToKill = (attacker->getCount() * singleCreatureKillChancePercent + 99) / 100;
int maxToKill = vstd::divideAndCeil(attacker->getCount() * singleCreatureKillChancePercent, 100);
vstd::amin(killedCreatures, maxToKill);
killedCreatures += (attacker->level() * attacker->valOfBonuses(BonusType::DEATH_STARE, BonusCustomSubtype::deathStareCommander)) / defender->level();