Sql server T-SQL舍入问题

Sql server T-SQL舍入问题,sql-server,tsql,Sql Server,Tsql,我需要帮助解决T-SQL中小数舍入的问题 我计算的金额:7.34和6.16-列数据类型smallmoney 计算中使用的百分比:56.25和43.75-列数据类型十进制(5,2) 使用CAST((金额*百分比)/100)作为数字(36,2))会产生6.16美元的问题,因为四舍五入时,总数显示为6.17美元。我尝试过其他数据类型smallmoney、money、decimal,也遇到了同样的问题 示例代码 -- Smallmoney and Decimal(5,2) are datatypes o

我需要帮助解决T-SQL中小数舍入的问题

我计算的金额:7.34和6.16-列数据类型smallmoney

计算中使用的百分比:56.25和43.75-列数据类型十进制(5,2)

使用CAST((金额*百分比)/100)作为数字(36,2))会产生6.16美元的问题,因为四舍五入时,总数显示为6.17美元。我尝试过其他数据类型smallmoney、money、decimal,也遇到了同样的问题

示例代码

-- Smallmoney and Decimal(5,2) are datatypes of the columns in the table

declare @amount smallmoney = 7.34
declare @amount2 smallmoney = 6.16
declare @percent1 decimal(5,2) = 56.25
declare @percent2 decimal(5,2) = 43.75
declare @total NUMERIC(36,2)
declare @total2 NUMERIC(36,2)

select cast((@amount * @percent1)/100  AS NUMERIC(36,2)) 
select cast((@amount * @percent2)/100  AS NUMERIC(36,2))

select cast((@amount * @percent1)/100  AS NUMERIC(36,2)) + cast((@amount * @percent2)/100  AS NUMERIC(36,2)) -- Total is 7.34. This is correct.

select cast((@amount2 * @percent1)/100  AS NUMERIC(36,2))
select cast((@amount2 * @percent2)/100  AS NUMERIC(36,2))


select cast((@amount2 * @percent1)/100  AS NUMERIC(36,2)) + cast((@amount2 * @percent2)/100  AS NUMERIC(36,2)) -- Total is 6.17. I need this to be 6.16
我需要制作一份总结和详细的报告。见下面的示例

drop table #percentconfig, #transdata, #detailreport

create table #percentconfig(Id int, Name varchar(30), perct decimal(5,2))
insert into #percentconfig values (1, '56 percent', 56.25)
insert into #percentconfig values (1, '43 percent', 43.75)

create table #transdata(transId int, LinkId int, Amount smallmoney)

insert into #transdata values (1, 1, 7.34)
insert into #transdata values (2, 1, 6.16)

select TransId, cast((t.Amount * p.perct)/100  AS NUMERIC(36,2)) as Amount 
into #detailreport
from #percentconfig p 
inner join #transdata t on p.Id = t.LinkId -- This is my query that produces the report.

select TransId, Amount from #transdata -- Summary Report
select TransId, Amount TotalAmount from #detailreport -- Detail Report

关于如何处理这个问题,你有什么想法吗?

问题其实不是关于数据类型,而是关于取整本身

我们可以将您的测试用例简化为:

select cast(0.465 as numeric(36,2)) 
     + cast(0.535 as numeric(36,2))
两个原始值之和为
1
。但是,将两个四舍五入的值相加并不能保证返回原始值。第一个
cast
返回
0.47
,第二个返回
0.54
,因此总和的计算结果为
1.01

如果您想要安全的往返,请先求和,然后计算:

select cast(0.465 + 0.535 as numeric(36,2))
对于您的示例查询,这将是:

select cast(
    @amount2 * @percent1 / 100 + @amount2 * @percent2 /100  
    as numeric(36,2)
)

如果您的要求是所有结果必须与原始值相加(通常用于计算税收),那么您就不能对每个值进行四舍五入。
您需要查看上一次计算的四舍五入,并根据上一次四舍五入的工作方式向上或向下四舍五入。
对于2个百分比的计算,以下公式适用:

declare @amount2 smallmoney = 6.16
declare @percent1 decimal(5,2) = 56.25
declare @percent2 decimal(5,2) = 43.75

select cast((@amount2 * @percent1)/100  AS NUMERIC(36,2))
--select cast((@amount2 * @percent2)/100  AS NUMERIC(36,2))
select cast(@amount2 - cast((@amount2 * @percent1)/100  AS NUMERIC(36,2)) AS NUMERIC(36,2))

select cast((@amount2 * @percent1)/100  AS NUMERIC(36,2)) + cast(@amount2 - cast((@amount2 * @percent1)/100  AS NUMERIC(36,2)) AS NUMERIC(36,2)) -- The total here 6.17. I need 6.16

最近发布了一个非常类似的问题-请参见此处的问题 我的答案在这里

还请注意(例如,
十进制(5,2)
数字(5,2)
相同),因此使用哪一种并不重要

关键是十进制(包括“货币”类型)是精确的,因为一旦它计算出一个值,比如说,6.66,那么它就精确地处理为6.66。例如,这意味着(20.0/3)*3可能最终为19.999998,因为它认为20.0/3=6.666666(取决于它使用的小数点的数量)

在您的示例中,当您计算

select cast((@amount2 * @percent1)/100  AS NUMERIC(36,2)) + cast((@amount2 * @percent2)/100  AS NUMERIC(36,2))
  • 它计算(并舍入到精确值)每个组件
  • 然后将它们相加
这里最简单的选择是先将它们相加,然后在最后四舍五入到美元值,例如

select cast( ((@amount2 * @percent1)/100 + (@amount2 * @percent2)/100) AS NUMERIC(36,2))
但是,在进行转换/舍入时,您需要非常小心。对于现金(例如手头的钱),这实际上是合适的——你不能给别人半分钱。然而,对于银行和%交易而言,这可能是个问题

我建议尝试使用
float
为需要不精确值(如6和2/3)的计算键入变量,然后在需要“具体”时将其转换为
decimal

使用
float
值/数据类型的另一种方法是使用小数点(精度更高)多于所需的
decimal

例如,如果您的数据以美元和美分为单位,您可以使用
十进制(18,4)
进行计算,因此它有4个小数点-有效地以1/100美分进行计算。然后,当您将其设置为如上所述的“具体”时,将其四舍五入为2个小数点,例如,
decimal(18,2)
表示精确的美元和美分

请注意,上面的数据集会出现精度更高的小数点,例如,当使用百分比进行计算时,它会增加精度以获得比您需要的小数点更多的小数点,例如,
select(@amount2*@percent1)/100
结果为
3.4650000000

但是,如果您使用的是重复小数的值(例如,将20美元的午餐账单分成3人),则自动精度更新将无法处理,并将四舍五入到小数。如果你只需要除以100,这不会是一个问题,因为它只会将小数位移2位。然而,如果你想要每周平均值(除以52)或每天平均值(除以365等),很可能你会遇到一些取整问题

因此,您需要意识到在计算中的什么时候使用了什么类型/精度的值,并对它们进行控制,以确保它们代表了现实生活中需要的值

策略是使用更高精度的小数或浮点数进行计算,将更详细的分数考虑在内,然后在适当时对值进行舍入/转换。 e、 g

  • 你是一家商店,在某些商品上打折30%(并且以美元和美分计价)
  • 您将30%的折扣计算为
    float
    或高精度
    decimal
    值,这样您就可以得到准确的金额,而不是有人滥用它在您得到总额时额外获得几美分(或者,他们没有得到足够大的折扣)
  • 当你算出总数时,你把它转换成十进制(18,2),因此它是精确的,因为它代表了你向客户收取的实际金额。如果您将其保留为
    浮动
    ,则其值中可能有一个虚幻的分数,这会在您计算全天销售总额时产生一个错误

根据Pitor和GMB的建议,我已将代码更改如下。这给出了预期的结果

drop table #percentconfig, #transdata, #detailreport, #detailreport2

create table #percentconfig(Id int, Name varchar(30), perct decimal(5,2))
insert into #percentconfig values (1, '56 percent', 56.25)
insert into #percentconfig values (1, '43 percent', 43.75)

create table #transdata(transId int, LinkId int, Amount smallmoney)

insert into #transdata values (1, 1, 7.34)
insert into #transdata values (2, 1, 6.16)

select TransId, t.Amount AS FullAmt, p.Perct as Percentage, 
CASE WHEN (100 - p.perct) > p.perct THEN cast(t.Amount - cast((t.Amount * (100 - p.perct))/100  AS NUMERIC(36,2)) AS NUMERIC(36,2)) 
ELSE CAST(t.Amount * p.perct/100  AS NUMERIC(36,2)) END as Amount
into #detailreport
from #percentconfig p 
inner join #transdata t on p.Id = t.LinkId -- This is my query that produces the report.

select * from #transdata -- Summary Report
select * from #detailreport order by transId-- Detail Report

在代码< > 6.16美元<代码>中,你认为错误的结果是什么?我已经用示例代码进一步澄清了这一点:“这在某些情况下是有效的,但并不总是”SQL不返回不同的结果,如果你有相同的输入值和设置,它将返回相同的值。这意味着,如果它有时有效,有时无效,那么情况就不同了。至于图像,即电子表格,而不是SQL,Excel的舍入可能与SQL Server不同;特别是当它不是四舍五入而是截断(这是一件非常不同的事情)的时候,我的意思是,CAST-AS-NUMERIC(36,2)适用于7.34,但不适用于6.16。对于我们计算的任何金额,代码都是相同的。对于计算后的金额,如6.16