2017-01-11 15 views
1

Я столкнулся с проблемой точности при расчете рациона между двумя единицами измерения.Linq to SQL округление десятичного числа после деления

Ratio хранятся в базе данных SQL Server в UnitOfMeasureTable как: NumberOfDefaultUnits десятичного (28,18)

В моем примере LINQPad У меня есть класс, чтобы продемонстрировать режим, что происходит не так.

private class Test 
{ 
    public decimal saleUom { get; set; } 
    public decimal piUom { get; set; } 

    public decimal RatioRight { get; set; } 
    public decimal RatioWrong { get; set; } 
    public decimal CalcledAlsoRight { get { return saleUom/piUom; } } 
} 

недействительным Main() {

var xx = from uom in UnitsOfMeasures.Where(d=> d.Id == 9) 
     let buyUom = uom.NumberOfDefaultUnits 
     let sellUom = UnitsOfMeasures.First(d=> d.Id == 13).NumberOfDefaultUnits 
     select new Test 

{ 
    saleUom = sellUom, 
    piUom = buyUom, 
    RatioRight = sellUom/(buyUom * 1m), 
    RatioWrong = sellUom/buyUom, 
}; 

xx.First().Dump(); 

}

Результаты:

saleUom 453.592370000000000000 
piUom 1000000.000000000000000000 
RatioRight 0.000453592370000000000 
RatioWrong 0.0004535923 
CalcledAlsoRight 0.00045359237 

потребовалось некоторое время, чтобы выяснить, нужно умножить делитель на 1 м, чтобы получить правильный результат. Он становится еще более странным, если вы умножаете sellUom на 1 м. Тогда результат:

RatioRight = (sellUom * 1m)/(buyUom) 
RatioRight 0.000453 

Я предполагаю, что это что-то делать с тем, как SQL Server, магазинами десятичной (28,18) и как Linq преобразует команду деления.

Update: Все значения являются десятичными enter image description here

Update 2: Похоже, это целиком и полностью SQL округления вещи. Удаление .Net из уравнения

select top 1 uom.NumberOfDefaultUnits 
     from UnitOfMeasures uom 
     where uom.Id = 13 

select (select top 1 uom.NumberOfDefaultUnits 
     from UnitOfMeasures uom 
     where uom.Id = 13) 
    /
     (select top 1 uom.NumberOfDefaultUnits 
     from UnitOfMeasures uom 
     where uom.Id = 9) 

Первый запрос возвращает: 453,592370000000000000

Второе: 0,0004535923

+1

Похож, что 'buyUom' является' integer'. Я готов поспорить, что это имеет какое-то отношение к этому. Если вы умножаете его на '1m', он преобразуется в' decimal'. Я не уверен в этом случае, каков эффект деления десятичной дроби с целым числом. – HoneyBadger

+0

@HoneyBadger Nope, buyUom имеет дефолиал. он поступает прямо из столбца Decimal (28,18) в таблице UnitOfMeasure. Добавить скриншот –

+0

Вы можете посмотреть, что такое ценность 'buyUom'? Это может отличаться от значения при присвоении 'piUom' (по отношению к значимым цифрам). – HoneyBadger

ответ

3

Это, безусловно, из-за того, как SQL Server обрабатывает масштаб точности & в расчетах.

Если изменить пример @mikeymouses использовать шкалу 6 для @ p1 вы получите стабильные результаты:

DECLARE @p2 Decimal(28,6) = 1000000 
DECLARE @p3 Decimal(28,18) = 453.59237 

select @p3/@p2 

DECLARE @p1 Decimal(19,18) = 1 -- This is how Linq sends the * 1m 

select @p3/(@p2 *@p1) 
select (@p3 *@p1)/@p2 

Результаты:

0,0004535923700000000000

+0,00045359237000

0.00045359237000000000

точность и масштаб результаты документируются на MSDN, но ключевые моменты: Для Умножение:

  • точность результата = p1 + p2 + 1
  • Масштаб результата = s1 + s2

Для подкласса:

  • Точность результата = р1 - s1 + s2 + макс (6, S1 + р2 + 1)
  • Масштаб результата = макс (6, S1 + р2 + 1)

где P1 и P2 являются точность операндов и s1, s2 - масштаб операндов.

Следует помнить, что точность и масштаб результата имеют абсолютный максимум 38. Если точность результата больше 38, соответствующая шкала уменьшается, чтобы исключить усечение интегральной части результата. Я думаю, что это то, что здесь происходит.

+0

Спасибо за это. После немного большего количества игр, это похоже на то, что происходит. –

1

Похоже, это просто как SQL сделки с этими заявленных десятичных размеров деления на Афоризм

DECLARE @p2 Decimal(28,18) = 1000000 
DECLARE @p3 Decimal(28,18) = 453.59237 

select @p3/@p2 

DECLARE @p1 Decimal(19,18) = 1 -- This is how Linq sends the * 1m 

select @p3/(@p2 *@p1) 
select (@p3 *@p1)/@p2 

Результаты:

0,0004535923

0,000453

0.00045359