在 Javascript、Excel、Python 和科学计算器中,模运算
-34086.1576962834 % 360.0 <-- python
=MOD(-34086.1576962834, 360.0) <-- Excel function
给出一个值113.84230371659942
。(或者非常接近的东西,取决于小数位数和四舍五入。)
但是在 T-SQL 中,
Select -34086.1576962834 % 360.0
返回值-246.1576962834
。
我试过使用
Select cast(-34086.1576962834 as numeric(30,15)) % cast(360.0 as numeric(30,15))
正如我所想,这可能是浮点数与固定十进制数学不匹配的一个例子,但这并不能解决问题。
为什么 SQL Server 不以与计算器/预期结果一致的方式执行此操作?更重要的是,有没有办法让 T-SQL 像我的计算器一样进行算术运算?
我主要使用 SQL Server 2019,但我已经确认 SQL Server 2012 和 2014 也给出相同的 -246... 结果。
Oracle 函数mod(x,y)
和 pythonmath.fmod(x,y)
给出相同的 -246.nnn 结果。
根据维基百科,SQL 标准实现了
%
运算符的截断版本,对于数字a = nq + r,其中q是整数,将余数r定义为:r = a - n [ a / n ] 其中 [] 是整数部分函数(整数部分)1
因此,将示例代入方程式可得
因此,根据 SQL 标准,该
%
函数按预期工作。请参阅以类似方式实现标准的Postgres和MySQL 。如果您希望在被除数为负时通过使用底数除法而不是整数部分的方法获得相同的结果,则可以将除数添加到结果中,如 [ x ] + 1 = FLOOR( x ) 当x为负数时。
或者,如本SO 答案所示,无论股息是正数还是负数 2
(n + a % n) % n)
,都将转换为底线除法。这是 SQL 标准必须说的:(感谢ypercubeᵀᴹ和这篇博文):
因此,您会注意到很多“零刻度”,但这并不相关,就好像我们从N、M和R中删除了该限制一样,计算没有改变。移动我们有:
R = N - M * K与 || R || < || 男|| 和 SIGN( R ) = SIGN( N )。
这不是math.stackexchange.com,所以我不会做正式证明,但这两个限制意味着K在这种情况下是 [ N / M ] 而不是 FLOOR( N / M )。
使用您的示例,我们有:
1这与
FLOOR
应用于负值时的函数不同。2证明作为练习留给读者,因为作者已经 10 多年没有接触过数学教科书了。
这似乎是 SQL Server 如何使用负数计算模数的问题。
你会注意到,如果你拿出你的科学计算器(我使用的是 Windows 内置的
calc.exe
科学模式),并计算正商和负商,你将得到 113.nnn 和 246。 nnn 号码:如果我们分解模数数学并“从长远来看”,那么我们可以看到 SQL Server 在做什么:
恕我直言,这是一个错误
SQL Server 总是做 a
FLOOR
来计算商,而不是总是向零四舍五入。在我看来,这是一个错误,因为数学规则告诉我们应该向零舍入,而不是向下舍入。维基百科关于“模运算”的文章表明,对“模”的含义有不同的解释,尤其是在转向实数时——尽管自然数对模有更普遍理解的定义。
根据上面链接的维基百科文章,ISO SQL 标准要求使用“截断除法”公式:
请注意,这需要向零舍入。这是大多数人所期望的行为,因为在四舍五入时,许多人觉得向零四舍五入是“自然的”。
相反,SQL Server 使用“floored division”方法:
请注意,这需要向下舍入。
微软可能不会改变行为
SQL Server 长期以来一直存在这种行为,我不希望 SQL Server 更改其实现以使用 ISO SQL 标准为
%
和调用的“截断除法”公式mod()
。现在更改此功能将导致依赖现有行为的多年代码被破坏。相反,我希望微软能够清楚地记录现有功能,以明确他们使用哪种方法计算模数。如果我们幸运的话,他们可能会引入一种新的语法,也可以使用其他方法执行取模,这样人们就可以根据需要选择合适的行为。