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

У меня возникают неожиданные результаты при работе с SqlDecimals. В конце концов, кажется, что это сводится к проблемам масштабирования при делении 2 SqlDecimals.

Пример кода С# выглядит так:

[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: я не могу предсказать, какие комбинации числовых (p, s) будут переданы функции, но 38,8 «приемлемо». Я надеялся сделать более быструю SQLCLR-версию моего исходного UDF (который делает гораздо больше, чем просто делит btw =). По общему признанию, это быстрее, но это должно работать для того же диапазона чисел.


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 байта, как и числовое (38,8) использует 4 байта в SQL. У меня возникли проблемы с тем, что, по-видимому, он обрабатывает эти 4 байта по-разному в SQL (собственный) и в .Net (класс SqlDecimal), что, с моей точки зрения, ну... изумительно!   -  person deroby    schedule 19.07.2012
comment
deroby - проверьте это - SqlDecimal использует FOUR четырехбайтовые целые числа, а не только четыре байта. Да, нативные реализации математики под капотом между типами SQL .NET и самим SQL Server различаются...   -  person David W    schedule 19.07.2012
comment
Ты прав! Хм, не уверен, откуда я взял 4 байта, возможно, я как-то неправильно перевел их где-то в своей голове, поскольку в документации четко указано, что на самом деле требуется 17 байтов для хранения числа с точностью 29-38. Структура SqlDecimal использует 4 целых числа без знака, что сводится к 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