有没有一种算法可以将一个数字分成三个部分,使它们的总和等于原始数字?

10
例如,如果您考虑以下示例。
100.00 - Original Number
33.33  - 1st divided by 3
33.33  - 2nd divided by 3
33.33  - 3rd divided by 3
99.99  - Is the sum of the 3 division outcomes

But i want it to match the original 100.00

我看到一种做法是将原数减去前两个商,结果就是第三个数字。现在,如果我把这三个数字取出来,就可以得到原数。

 100.00 - Original Number
  33.33 - 1st divided by 3
  33.33 - 2nd divided by 3
  33.34 - 3rd number
 100.00 - Which gives me my original number correctly. (33.33+33.33+33.34 = 100.00)

在Oracle PL/SQL或函数中是否有一个公式可以实现这个功能?

提前致谢!


我不知道有这样的事情。任何库或函数怎么知道要向哪个数字随意添加额外的一分钱呢?听起来你已经被“一分钱bug”感染了。 - jgitter
1
你可能会对最近的一个类似问题感兴趣,它是关于将价格除以3的(虽然是针对C#而不是PL/SQL)Dividing prices by 3 - Kevin
2
你不是刚刚指定了“算法”吗?N/3,N/3,N-2(N/3) - Leeor
1
我在一个预算系统中遇到了这个确切的问题 - 他们想要将年度预算金额尽可能均匀地分配到12个月中,任何舍入误差都应用于第12个月。 - Jeffrey Kemp
1
@user2025696,仅供参考,如果您的问题陈述了实际需求以及匹配的样本数据,那么会更好。您的样本输出是具有误导性的。但是,感谢您提出这个有趣的问题,很有意思 :) - Jeffrey Kemp
显示剩余4条评论
4个回答

7

这个版本也将精度作为参数:

with q as (select 100 as val, 3 as parts, 2 as prec from dual)
select rownum as no
      ,case when rownum = parts
       then val - round(val / parts, prec) * (parts - 1) 
       else round(val / parts, prec)
       end v
from   q
connect by level <= parts

no  v
=== =====
1   33.33
2   33.33
3   33.34

例如,如果您想将值分配在当前月份的天数之间,可以这样做:
with q as (select 100 as val
                 ,extract(day from last_day(sysdate)) as parts
                 ,2 as prec from dual)
select rownum as no
      ,case when rownum = parts
       then val - round(val / parts, prec) * (parts - 1) 
       else round(val / parts, prec)
       end v
from   q
connect by level <= parts;

1   3.33
2   3.33
3   3.33
4   3.33
...
27  3.33
28  3.33
29  3.33
30  3.43

为了按照每个月的天数进行加权分配价值,您可以采用以下方法(将 level <= 3 更改为更改计算月数的数量):
with q as (
  select add_months(date '2013-07-01', rownum-1) the_month
        ,extract(day from last_day(add_months(date '2013-07-01', rownum-1)))
         as days_in_month
        ,100 as val
        ,2 as prec
  from dual
  connect by level <= 3)
,q2 as (
  select the_month, val, prec
        ,round(val * days_in_month 
                     / sum(days_in_month) over (), prec)
         as apportioned
        ,row_number() over (order by the_month desc) 
         as reverse_rn
  from   q)
select the_month
      ,case when reverse_rn = 1
       then val - sum(apportioned) over (order by the_month
                  rows between unbounded preceding and 1 preceding)
       else apportioned
       end as portion
from q2;

01/JUL/13   33.7
01/AUG/13   33.7
01/SEP/13   32.6

Jeffrey,这看起来非常有前途!我只是有一个后续问题。如果“部分”不同怎么办?比如说“一个月的天数”?例如“(Value/TotalDays)*NumberOfDaysInMonth”。基本上,我会根据该月份的天数将价值分成3个月。 - user2025696
没问题,只需将天数替换为查询中的变量即可。您可以使用LAST_DAY函数来计算任何月份的天数。 - Jeffrey Kemp
@user2025696,我已经回滚了您的更改,因为您实际上是在对此答案发表评论。请使用评论部分进行此类问题/澄清。编辑答案是为了改善它现有的内容。 - Nick Rippe
1
你需要将部件数量(例如一个月的天数)放入参数中,不要将其替换为查询本身,否则会导致不一致 - 你实际上在输出的每一行中使用了不同数量的“部件”,因此我预计结果会不正确。请参见我的编辑。 - Jeffrey Kemp
1
在您的情况下,您想根据一个权重来分配金额,该权重与月份中的天数成比例。这有点棘手 - 我正在努力解决。 - Jeffrey Kemp
显示剩余2条评论

3

使用有理数。您可以将数字存储为分数而不是简单的值。这是确保数量真正分成3份并且总和等于原始数字的唯一方法。当然,您可以使用舍入和余数来进行某些hacky操作,只要您不在乎部分不完全分成3份。

"算法"很简单

100/3 + 100/3 + 100/3 == 300/3 == 100

将分子和分母存储在单独的字段中,然后添加分子。您可以在显示值时始终转换为浮点数。

Oracle文档甚至有一个很好的例子说明如何实现它:

CREATE TYPE rational_type AS OBJECT
( numerator INTEGER,
  denominator INTEGER,
  MAP MEMBER FUNCTION rat_to_real RETURN REAL,
  MEMBER PROCEDURE normalize,
  MEMBER FUNCTION plus (x rational_type)
       RETURN rational_type);

好主意,直到用户说“请将数字报告为带有2个小数位的十进制数 - 并且数字仍必须总计为100” :) - Jeffrey Kemp
用于报告值的格式不需要与存储它们的格式相同。我想为此提供一些背景可能会很有用。 - nont
同意,根据实际需求这可能是更好的答案 - 但是OP已经很清楚地表达了他们的期望输出。 - Jeffrey Kemp

1
这是一个带参数的SQL版本。
  SELECT COUNT (*), grp
    FROM (WITH input AS (SELECT 100 p_number, 3 p_buckets FROM DUAL),
               data
               AS (    SELECT LEVEL id, (p_number / p_buckets) group_size
                         FROM input
                   CONNECT BY LEVEL <= p_number)
          SELECT id, CEIL (ROW_NUMBER () OVER (ORDER BY id) / group_size) grp
            FROM data)
GROUP BY grp

输出:

COUNT(*)    GRP
33          1
33          2
34          3

如果您编辑输入参数(p_number和p_buckets),SQL基本上会将p_number尽可能均匀地分配给所请求的桶数(p_buckets)。

0

我昨天通过从起始数字减去3个部分中的2个来解决了这个问题,例如 100 - 33.33 - 33.33 = 33.34,将其相加的结果仍为 100。


2
相当棘手。现在想象一下,不是3个部分,而是357个部分。 :) - Yaroslav Shabalin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接