Skip to content

Commit

Permalink
修正了数值比例分摊算法的一些原先不太合理的地方
Browse files Browse the repository at this point in the history
  • Loading branch information
guogangj committed Nov 29, 2024
1 parent 8bd2793 commit ee77276
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 16 deletions.
4 changes: 2 additions & 2 deletions AlgorithmLib/AlgorithmLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Version>1.0.5</Version>
<Version>1.0.6</Version>
<PackageId>AlgorithmLib</PackageId>
<Authors>Jiang Guogang</Authors>
<Company></Company>
<Description>一些常用的算法</Description>
<RepositoryUrl>https://github.com/guogangj/AlgorithmLib.git</RepositoryUrl>
<PackageReleaseNotes>增加了RelationshipHelper.MakeRelationShip方法,用于处理整体对象和碎片对象的关联关系</PackageReleaseNotes>
<PackageReleaseNotes>修正了数值分摊算法之前一些不合理之处</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/guogangj/AlgorithmLib</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
Expand Down
55 changes: 42 additions & 13 deletions AlgorithmLib/NumberDivideHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public static decimal[] Divide(decimal total, decimal[] scale, int decimalNum =
throw new ArgumentException("Cannot divide the number due to its decimal number is more than specified value");
}
int partNum = scale.Length;
decimal[] result = new decimal[partNum];

decimal[] result = new decimal[partNum]; //要返回的结果(可能需要舍入)


if (partNum == 0) {
return result;
}
Expand All @@ -43,31 +46,57 @@ public static decimal[] Divide(decimal total, decimal[] scale, int decimalNum =
decimalNum = -1;
}

decimal[] rawResult = new decimal[partNum]; //未经舍入的原始分摊结果

//尝试执行分摊
for (int i = 0; i < partNum; i++) {
result[i] = total * scale[i] / scaleSum;

rawResult[i] = total * scale[i] / scaleSum;
if (decimalNum != -1) {

result[i] = decimal.Round(result[i], decimalNum);
result[i] = decimal.Round(rawResult[i], decimalNum);
}
else {
result[i] = rawResult[i];
}
}

decimal totalTest = result.Sum();
decimal remainder = total - totalTest;
if (remainder == 0m) {
decimal totalRes = result.Sum();
decimal totalDiff = total - totalRes;
if (totalDiff == 0m) {
return result;
}

//摊到最大的上面
int maxIdx = 0;
for (int i = 1; i < result.Length; i++) {
if (result[i] > result[maxIdx]) {
maxIdx = i;
if (decimalNum == -1) {
//如果小数位不限,产生的误差就算有也极小,直接把误差值算到差距最大的一个值上即可
int maxDiffIdx = 0;
for (int i = 1; i < partNum; i++) {
if (Math.Abs(result[i] - rawResult[i]) > Math.Abs(result[maxDiffIdx] - rawResult[maxDiffIdx])) {
maxDiffIdx = i;
}
}
result[maxDiffIdx] += totalDiff;
}
result[maxIdx] += remainder;
else {
//平掉由于舍入引起的误差
//用差值来排个序,准备从差值大的开始调整
var sortedDiffs = totalDiff > 0
? rawResult.Select((r, i) => new { Index = i, Residual = r - result[i] }).ToArray().OrderByDescending(x => x.Residual).ToArray()
: rawResult.Select((r, i) => new { Index = i, Residual = r - result[i] }).ToArray().OrderBy(x => x.Residual).ToArray();

//最小调整单元,根据小数位决定
decimal unit = 1m / (decimalNum >= 0 ? (decimal)Math.Pow(10, decimalNum) : 1m);

int idx = 0;
while (totalDiff != 0) {
int index = sortedDiffs[idx].Index;
decimal adjustment = totalDiff > 0 ? unit : -unit;
if (result[index] + adjustment >= 0) {
result[index] += adjustment;
totalDiff -= adjustment;
}
idx = (idx + 1) % sortedDiffs.Length; //按道理应该不会出现idx比数组长度大的情形,这里的取模应该是不需要的
}
}
return result;
}
}
Expand Down
11 changes: 11 additions & 0 deletions AlgorithmLibDemo/Demos.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AlgorithmLib;
using System;
using System.Diagnostics;
using System.Linq;

namespace AlgorithmLibDemo;

Expand Down Expand Up @@ -111,6 +112,7 @@ public static void NumberDivideDemo() {
void PrintDivInfo() {
Utils.SimplePrintArray(ratioArray, $"\n以此比例分摊 [{toDivide}] (小数位{decimalNum}): ");
}

PrintDivInfo();
Utils.SimplePrintArray(NumberDivideHelper.Divide(toDivide, ratioArray, decimalNum));

Expand Down Expand Up @@ -154,6 +156,15 @@ void PrintDivInfo() {
catch (ArgumentException ex) {
Console.WriteLine("出错了: " + ex.Message);
}

//一个实际的分摊案例
toDivide = 26;
decimalNum = 3;
ratioArray = new[] { 0.001m, 0.001m, 0.001m, 0.0022m, 0.001m, 0.0022m, 0.001m, 0.0011m, 0.0011m, 0.001m, 0.0011m, 0.0022m, 0.0011m, 0.001m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.001m, 0.0022m, 0.0022m, 0.001m, 0.0011m, 0.0105m, 0.0111m, 0.0022m, 0.0022m, 0.0011m, 0.0022m, 0.001m, 0.001m, 0.0011m, 0.001m, 0.0011m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0011m, 0.001m, 0.0011m, 0.0011m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0011m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.001m, 0.001m, 0.001m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.0011m, 0.0022m, 0.0022m, 0.0022m, 0.0022m, 0.001m, 0.0011m, 0.0011m, 0.0011m, 0.0011m, 0.001m, 0.0022m, 0.0022m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0011m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0022m, 0.001m, 0.0011m, 0.001m, 0.001m, 0.001m, 0.0022m, 0.0022m, 0.001m, 0.001m, 0.0022m, 0.0022m, 0.001m, 0.001m, 0.0022m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0022m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.001m, 0.0012m, 0.0012m, 0.001m, 0.001m, 0.001m, 0.001m };
Console.WriteLine("\n一个实际出现的例子,特点是:数值多,比例值小且小数位多,但结果舍进保留位数却少,这可能引起较大的分摊误差");
decimal[] result = NumberDivideHelper.Divide(toDivide, ratioArray, decimalNum);
Utils.SimplePrintArray(result);
Console.WriteLine(result.Sum());
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion AlgorithmLibDemo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class DemoItem {
public DemoFun Fun { get; set; }
public string Desc { get; set; }
}


static void Main(string[] args) {

Expand Down

0 comments on commit ee77276

Please sign in to comment.