c#SqlDecimal在小数字的乘法中翻转符号

der*_*oby 7 c# sql-server sqlclr

看来我正在用'我最喜欢的数据类型'SqlDecimal遇到更多困境.我想知道这是否应该被认为是一个错误.

当我在SQL中乘以两个小数字时,我得到了预期的结果.当我通过SQLCLR函数运行相同的数字时,结果非常令人惊讶.

c#代码:

using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

namespace TestMultiplySQLDecimal
{
    public static class Multiplier
    {
        [SqlFunction(DataAccess=DataAccessKind.None, IsDeterministic = true,IsPrecise = true)]
        public static SqlDecimal Multiply(SqlDecimal a, SqlDecimal b)
        {
            if (a.IsNull || b.IsNull) return SqlDecimal.Null;
            return a*b;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

SQL代码:

USE tempdb
GO
IF DB_ID('test') IS NOT NULL DROP DATABASE test
GO
CREATE DATABASE test
GO
USE test
GO

CREATE ASSEMBLY TestMultiplySQLDecimal 
FROM 'C:\Users\tralalalaa\Documents\visual studio 2015\Projects\TestMultiplySQLDecimal\TestMultiplySQLDecimal\bin\Release\TestMultiplySQLDecimal.dll'
WITH PERMISSION_SET = SAFE
GO

CREATE FUNCTION dbo.fn_multiply(@a decimal(38,8), @b decimal(18,8)) 
RETURNS decimal(38,8)
EXTERNAL NAME TestMultiplySQLDecimal.[TestMultiplySQLDecimal.Multiplier].Multiply
GO
DECLARE @a decimal(38, 8),
        @b decimal(18, 8),
        @c decimal(38, 8),
        @f decimal(38, 8)

SELECT @a = -0.00000450,
       @b = 0.193,
       @c = NULL,
       @f = NULL

SELECT @c = @a * @b,
       @f = dbo.fn_multiply(@a, @b)

SELECT multiply = null, c = @c, f = @f
Run Code Online (Sandbox Code Playgroud)

结果是:c = -0.00000100 f = +0.00000100

我知道"绝对"的差异是"最小的"而且我"淡化"了更大的错误,将其归咎于"四舍五入的差异"......但是很难向客户解释消极时期的积极结果是积极的.毕竟,T-SQL支持它很好......

我可以尝试使用十进制(28,8)而不是十进制(38,8)解决它,但我会遇到其他(完全不相关)的问题然后=/


以下控制台应用程序表现出相同的问题,而不必涉及SQL Server/SQLCLR:

using System;
using System.Data.SqlTypes;

namespace PlayAreaCSCon
{
    class Program
    {
        static void Main(string[] args)
        {
            var dec1 = new SqlDecimal(-0.00000450d);
            var dec2 = new SqlDecimal(0.193d);
            dec1 = SqlDecimal.ConvertToPrecScale(dec1, 38, 8);
            dec2 = SqlDecimal.ConvertToPrecScale(dec2, 18, 8);
            Console.WriteLine(dec1 * dec2);
            Console.ReadLine();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

打印 0.000001

Dam*_*ver 5

我相信这个漏洞大约位于1550SqlDecimal左右:

ret = new SqlDecimal(rgulRes, (byte)culRes, (byte)ResPrec, 
                     (byte)ActualScale, fResPositive);

if (ret.FZero ())
     ret.SetPositive();

ret.AssertValid();

ret.AdjustScale(lScaleAdjust, true);

return ret;
Run Code Online (Sandbox Code Playgroud)

它首先使用final scale参数构造一个新的小数.接下来根据传入的构造函数参数检查结果是否为"零".

然后,在断言所有内容都有效后,它会执行比例调整.

在执行FZero检查时,结果类似于-0.0000008685.我们知道最终的比例将是6,因为我们在规模和精度上达到了极限.那么,前6位数都是零.

只有在此之后,当调整比例时,才需要考虑舍入并将其移动1到最后的小数位.

这是一个错误.遗憾的是,SQL Server本机实现的源代码decimal不公开,因此我们无法将其与托管实现进行比较,SqlDecimal以查看它们的相似程度以及原始程序如何避免相同的错误.