好的。我很确定原始算法(如书面)和发布的代码(如书面)并不能完全回复@Mathias 概述的测试用例的邮件。
我对这个算法的预期用途是稍微更具体的应用。而不是使用(@amt / @SumAmt) 计算百分比,如原始问题所示。我有一个固定的 $ 金额,需要根据为每个项目定义的百分比拆分来拆分或分布在多个项目中。拆分百分比总和为 100%,但是,直接乘法通常会导致小数(当强制四舍五入为整数时)不加起来我拆分的总金额。这是问题的核心。
我相当肯定@Dav 的原始答案在(如@Mathias 所描述的)舍入值在多个切片中相等的情况下不起作用。原算法和代码的这个问题可以用一个测试用例来概括:
取出 100 美元,以 33.333333% 作为百分比,将其分成 3 种方式。
使用@jtw 发布的代码(假设这是原始算法的准确实现),会产生为每个项目分配 33 美元的错误答案(导致总和为 99 美元),因此它未通过测试。
我认为更准确的算法可能是:
- 有一个从 0 开始的运行总计
- 对于组中的每个项目:
- 计算未四舍五入的分配量为
( [Amount to be Split] * [% to Split] )
- 计算累积余数为
[Remainder] + ( [UnRounded Amount] - [Rounded Amount] )
- 如果
Round( [Remainder], 0 ) > 1 OR当前项是列表中的最后一项,则设置该项的分配=[Rounded Amount] + Round( [Remainder], 0 )
- 其他设置项目的分配=
[Rounded Amount]
- 下一项重复
在 T-SQL 中实现,如下所示:
-- Start of Code --
Drop Table #SplitList
Create Table #SplitList ( idno int , pctsplit decimal(5, 4), amt int , roundedAmt int )
-- Test Case #1
--Insert Into #SplitList Values (1, 0.3333, 100, 0)
--Insert Into #SplitList Values (2, 0.3333, 100, 0)
--Insert Into #SplitList Values (3, 0.3333, 100, 0)
-- Test Case #2
--Insert Into #SplitList Values (1, 0.20, 57, 0)
--Insert Into #SplitList Values (2, 0.20, 57, 0)
--Insert Into #SplitList Values (3, 0.20, 57, 0)
--Insert Into #SplitList Values (4, 0.20, 57, 0)
--Insert Into #SplitList Values (5, 0.20, 57, 0)
-- Test Case #3
--Insert Into #SplitList Values (1, 0.43, 10, 0)
--Insert Into #SplitList Values (2, 0.22, 10, 0)
--Insert Into #SplitList Values (3, 0.11, 10, 0)
--Insert Into #SplitList Values (4, 0.24, 10, 0)
-- Test Case #4
Insert Into #SplitList Values (1, 0.50, 75, 0)
Insert Into #SplitList Values (2, 0.50, 75, 0)
Declare @R Float
Declare @Results Float
Declare @unroundedAmt Float
Declare @idno Int
Declare @roundedAmt Int
Declare @amt Float
Declare @pctsplit Float
declare @rowCnt int
Select @R = 0
select @rowCnt = 0
-- Define the cursor
Declare SplitList Cursor For
Select idno, pctsplit, amt, roundedAmt From #SplitList Order By amt Desc
-- Open the cursor
Open SplitList
-- Assign the values of the first record
Fetch Next From SplitList Into @idno, @pctsplit, @amt, @roundedAmt
-- Loop through the records
While @@FETCH_STATUS = 0
Begin
-- Get derived Amounts from cursor
select @unroundedAmt = ( @amt * @pctsplit )
select @roundedAmt = Round( @unroundedAmt, 0 )
-- Remainder
Select @R = @R + @unroundedAmt - @roundedAmt
select @rowCnt = @rowCnt + 1
-- Magic Happens! (aka Secret Sauce)
if ( round(@R, 0 ) >= 1 ) or ( @@CURSOR_ROWS = @rowCnt ) Begin
select @Results = @roundedAmt + round( @R, 0 )
select @R = @R - round( @R, 0 )
End
else Begin
Select @Results = @roundedAmt
End
If Round(@Results, 0) <> 0
Begin
Update #SplitList Set roundedAmt = @Results Where idno = @idno
End
-- Assign the values of the next record
Fetch Next From SplitList Into @idno, @pctsplit, @amt, @roundedAmt
End
-- Close the cursor
Close SplitList
Deallocate SplitList
-- Now do the check
Select * From #SplitList
Select Sum(roundedAmt), max( amt ),
case when max(amt) <> sum(roundedamt) then 'ERROR' else 'OK' end as Test
From #SplitList
-- End of Code --
这会产生以下测试用例的最终结果集:
idno pctsplit amt roundedAmt
1 0.3333 100 33
2 0.3333 100 34
3 0.3333 100 33
据我所知(我在代码中有几个测试用例),它可以非常优雅地处理所有这些情况。