Проблеми с SqlDecimal в SQLCLR проект

Имам някои неочаквани резултати, когато работя с SqlDecimals. В крайна сметка изглежда се свежда до проблеми с мащаба при разделянето на 2 SqlDecimals.

c# примерен код изглежда така:

[Microsoft.SqlServer.Server.SqlFunction]
[return: SqlFacet(Precision = 38, Scale = 8)]
public static SqlDecimal fn_divide([SqlFacet(Precision = 38, Scale = 8)]SqlDecimal x, [SqlFacet(Precision = 38, Scale = 8)]SqlDecimal y)
{
    var r = SqlDecimal.Divide(@x, @y);

    return r;
}

SQL тестовият код изглежда така:

DECLARE @x numeric(38, 8),
        @y numeric(38, 8)

SELECT @x = Replicate('1', 28) + '.12345678',
       @y = '0.25896314'

SELECT via = 'TSQL', x = @x, y = @y, r = Convert(numeric(38, 8),  @x / @y)
SELECT via = 'SQLCLR', x = @x, y = @y, r = dbo.fn_divide(@x, @y)

Вторият избор ми връща следната грешка:

A .NET Framework error occurred during execution of user-defined routine or aggregate "fn_divide": `
System.OverflowException: Arithmetic Overflow.
System.OverflowException: 
   at System.Data.SqlTypes.SqlDecimal.MultByULong(UInt32 uiMultiplier)
   at System.Data.SqlTypes.SqlDecimal.AdjustScale(Int32 digits, Boolean fRound)
   at System.Data.SqlTypes.SqlDecimal.op_Division(SqlDecimal x, SqlDecimal y)
   at System.Data.SqlTypes.SqlDecimal.Divide(SqlDecimal x, SqlDecimal y)
  at UserDefinedFunctions.fn_divide(SqlDecimal x, SqlDecimal y)

Търсейки в мрежата, намерих много проблеми, свързани с грешки при закръгляване при конвертиране на SqlDecimal в Decimal, но в моя случай всичко е SqlDecimal от началото до края, тъй като исках да избегна точно това. По същия начин бих се надявал, че резултатът ще бъде идентичен с начина, по който това е внедрено в SQL.. но уви. Дори когато не препълва, това ще ми даде различни резултати в „гранични случаи“.

Някой има ли съвети как да поправя това или поне да го заобиколя? SqlDecimal използва ли вътрешно (.Net) десетични числа и следователно не може да натъпче информацията в своите 3 байта?!?!?

PS: Не мога да предвидя какви комбинации от numeric(p,s) ще бъдат предадени на функцията, но 38,8 е „приемливо“. Надявах се да направя по-бърза SQLCLR версия на моя оригинален UDF (който прави много повече от просто разделяне между другото =). Вярно е, че е по-бърз, но трябва да работи за същия диапазон от числа.


person deroby    schedule 20.06.2012    source източник
comment
SqlDecimal използва вътрешно четири неподписани цели числа от четири байта, като дава максимална точност/мащаб от 38. Вижте msdn.microsoft.com/en-us/library/dbew96f6(v=vs.85).   -  person David W    schedule 18.07.2012
comment
Страхувам се, че връзката не работи, но все пак: да, знам, че използва 4 байта, както и Numeric(38,8) използва 4 байта в SQL. Частта, с която имам проблеми е, че - очевидно - третира тези 4 байта по различен начин в SQL (родния) спрямо .Net (SqlDecimal клас), което от моя гледна точка е добре... поразително!   -  person deroby    schedule 19.07.2012
comment
deroby - проверете дали - SqlDecimal използва ЧЕТИРИ четирибайтови int, а не само четири байта. Да, родните имплементации на математиката под капака между типовете SQL .NET и самия SQL Server се различават...   -  person David W    schedule 19.07.2012
comment
Прав си! Хм, не съм сигурен откъде взех 4-те байта, вероятно някак си съм го превел погрешно някъде в главата си, тъй като в документацията ясно се посочва, че всъщност са необходими 17 байта за съхраняване на число с точност 29-38. Структурата на SqlDecimal използва 4 int без знак, което се свежда до 16 байта. Не съм сигурен защо sql се нуждае от допълнителния байт, но предполагам, че е свързано с NULL-способност и знак и т.н., което вероятно също е част от структурата на SqlDecimal, но не е споменато... Както и да е, независимо от това как нещата се съхраняват вътрешно, аз доста съм разочарован, че не са използвали това, което е под капака в SQL първоначално =(   -  person deroby    schedule 21.07.2012
comment
@deroby По отношение както на този въпрос, така и на новия, който сте свързали с този: можете ли да публикувате някои примерни стойности, които всъщност използвате, а не само тестови стойности, които показват проблема? Както споменах във вашия свързан въпрос за умножение, чудя се защо пропускате DECIMAL(38, 8) на първо място. Напълно логично е да върнете този тип, или евентуално DECIMAL(38, 18), но дефиницията на входните параметри има много по-голямо влияние върху резултата, отколкото върнатият тип.   -  person Solomon Rutzky    schedule 30.01.2017
comment
Както бе споменато в другия въпрос, проблемът е най-вече, че функцията трябва да бъде достатъчно обща, за да се справи с „всеки“ вход. В миналото имахме чиста SQL функция, която работеше добре. От съображения за производителност преминахме към SQLCLR решение и забелязахме, че тази част се сблъска с проблеми, доказвайки, че SqlDecimal не е съвсем същото като err...a SQL Decimal. Дадените тук примери очевидно са „измислени“, но първо се натъкнахме на проблема с (по-) реалистични числа.   -  person deroby    schedule 31.01.2017