Prechádzať zdrojové kódy

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
Ivan Savenko 1 rok pred
rodič
commit
81e6207df0

+ 27 - 0
Global.h

@@ -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)
 	{

+ 2 - 1
lib/battle/DamageCalculator.cpp

@@ -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;

+ 2 - 2
lib/mapObjects/CGTownInstance.cpp

@@ -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

+ 1 - 1
lib/mapObjects/MiscObjects.cpp

@@ -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

+ 1 - 1
server/battles/BattleActionProcessor.cpp

@@ -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();