IProbabilityService /// <summary>
/// 一個抽象的機率服務。
/// </summary>
public interface IProbabilityService
{
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">機率百分比(如機率爲30%請傳入30)。</param>
/// <param name="getRandomNumber">獲取隨機數的委託(通常爲Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
bool IsHit(double probabilityPercentage, Func<double> getRandomNumber);
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">機率百分比(如機率爲30%請傳入30)。</param>
/// <param name="hasHitCount">命中次數。</param>
/// <param name="noHitCount">沒有命中的次數。</param>
/// <param name="getRandomNumber">獲取隨機數的委託(通常爲Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
bool IsHit(double probabilityPercentage, int hasHitCount, int noHitCount, Func<double> getRandomNumber);
}
ProbabilityServiceinternal sealed class ProbabilityService : IProbabilityService
{
#region Implementation of IProbabilityService
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">機率百分比(如機率爲30%請傳入30)。</param>
/// <param name="getRandomNumber">獲取隨機數的委託(通常爲Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
public bool IsHit(double probabilityPercentage, Func<double> getRandomNumber)
{
//若是機率大等於100則每次都命中。
if (probabilityPercentage >= 100)
return true;
//獲得機率的百分比。
probabilityPercentage = probabilityPercentage / 100;
return InternalIsHit(probabilityPercentage, getRandomNumber);
}
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">機率百分比(如機率爲30%請傳入30)。</param>
/// <param name="hasHitCount">命中次數。</param>
/// <param name="noHitCount">沒有命中的次數。</param>
/// <param name="getRandomNumber">獲取隨機數的委託(通常爲Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
public bool IsHit(double probabilityPercentage, int hasHitCount, int noHitCount, Func<double> getRandomNumber)
{
//若是機率大等於100則每次都命中。
if (probabilityPercentage >= 100)
return true;
//獲得機率的百分比。
probabilityPercentage = probabilityPercentage / 100;
//獲得總計算次數。
var totalCount = (double)(hasHitCount + noHitCount);
//獲得當前命中的機率。
var currentProbability = hasHitCount / totalCount;
//若是當前命中的機率大於傳入的機率則不會命中。
if (currentProbability > probabilityPercentage)
return false;
return InternalIsHit(probabilityPercentage, getRandomNumber);
}
#endregion Implementation of IProbabilityService
#region Private Method
/// <summary>
/// 是否命中。
/// </summary>
/// <param name="probabilityPercentage">不須要處理的百分比(介於 0.0 和 1.0 之間的數)。</param>
/// <param name="getRandomNumber">獲取隨機數的委託(通常爲Random.NextDouble())。</param>
/// <returns>是否命中。</returns>
private static bool InternalIsHit(double probabilityPercentage, Func<double> getRandomNumber)
{
//獲得一個隨機數。
var randomNumber = getRandomNumber();
if (randomNumber < 0 || randomNumber >= 1)
throw new ArgumentException("隨機數必須是一個介於 0.0 和 1.0 之間的數。");
//取後15位
const int places = 15;
//精簡小數位,提高几率準確性。
randomNumber = GetNumber(randomNumber, places);
return probabilityPercentage > randomNumber;
}
/// <summary>
/// 精簡數字的小數位。
/// </summary>
/// <param name="number">數字。</param>
/// <param name="places">小數位。</param>
/// <returns>精簡小數位後的數字。</returns>
private static double GetNumber(double number, int places)
{
//精簡小數位,提高几率準確性。
return Math.Round(number, places);
//該方法會提升準確性但會影響性能,適用於高精度場景。
/*var str = number.ToString(CultureInfo.InvariantCulture); if (!str.Contains(".")) return number; var t = str.Split('.'); number = double.Parse(t[0] + "." + string.Join("", t[1].Take(places))); return number;*/
}
#endregion Private Method
}
Programinternal class Program
{
private static void Main()
{
var random = new Random();
//每一次執行的測試次數(當前爲10w次)。
const int totalCount = 100000;
//機率百分比。
double probability;
#region GetProbability By Console "probability"
var promptMessage = "請輸入機率百分比,如30%:";
string probabilityString;
do
{
Console.WriteLine(promptMessage);
promptMessage = "請輸入一個正確的機率百分比:";
probabilityString = Console.ReadLine();
if (probabilityString != null && probabilityString.EndsWith("%"))
probabilityString = probabilityString.TrimEnd('%');
} while (!double.TryParse(probabilityString, out probability));
#endregion GetProbability By Console "probability"
Console.WriteLine("測試次數設定爲:{0},機率設定爲:{1}%", totalCount, GetPercentage(probability, 100));
Console.WriteLine("==================================================");
IProbabilityService probabilityService = new ProbabilityService();
while (true)
{
RunTest(totalCount, (i, hitCount) => probabilityService.IsHit(probability, random.NextDouble));
RunTest(totalCount, (i, hitCount) => probabilityService.IsHit(probability, hitCount, i - hitCount, random.NextDouble));
Console.ReadLine();
}
}
/// <summary>
/// 執行測試。
/// </summary>
/// <param name="totalCount">測試次數。</param>
/// <param name="hit">是否命中委託。</param>
private static void RunTest(int totalCount, Func<int, int, bool> hit)
{
//總命中次數。
var hitCount = 0;
for (var i = 0; i < totalCount; i++)
{
var isHit = hit(i, hitCount);
if (isHit)
hitCount++;
}
//機率百分比。
var percentage = GetPercentage(hitCount, totalCount);
Console.WriteLine("總次數:{0},命中次數:{1},機率{2}%", totalCount, hitCount, percentage);
}
/// <summary>
/// 獲取百分比。
/// </summary>
/// <param name="number1">數字1。</param>
/// <param name="number2">數字2。</param>
/// <returns>百分比。</returns>
private static double GetPercentage(double number1, double number2)
{
return (number1 / number2) * 100;
}
}