Добро пожаловать в форум, Guest >> Войти | Регистрация | Поиск | Правила | | В избранное | Подписаться | ||
Все форумы / Microsoft SQL Server |
![]() ![]() |
KellyLynch Member Откуда: Сообщений: 94 |
Я наивно думал, что чем больше в decimal отведено на “количество разрядов, которое может быть расположено справа от десятичного разделителя”, тем точнее будут вычисления. То есть я ожидал что тип “decimal (20, 11)” даст бОльшую (или уж по крайней мере не меньшую) точность чем decimal (18, 9). Был у меня кусок программы (SQL Server 2012) вроде такого CREATE TABLE #Tmp ( AmountToRelease decimal (18, 9) ,ShareToRelease decimal (18, 9) ,ShareToRelease1stApproach decimal (18, 9) ,Dest decimal (18, 9) ) INSERT INTO #Tmp ( AmountToRelease ,ShareToRelease ,ShareToRelease1stApproach ) VALUES ( 0.823673237, 0.123456781, 100.987654329 ) UPDATE #Tmp SET Dest = AmountToRelease * (ShareToRelease / ShareToRelease1stApproach) select * from #Tmp drop table #Tmp Он дал результат в поле #Tmp.Dest = 0.001006935. “Абсолютно точный” же результат должен быть “0.823673237 * (0.123456781 / 100.987654329) = 0.0010069354230626”. Поэтому я решил увеличить точность всех decimal с (18, 9) на (20, 11) и переписал код так: CREATE TABLE #Tmp ( AmountToRelease decimal (20, 11) ,ShareToRelease decimal (20, 11) ,ShareToRelease1stApproach decimal (20, 11) ,Dest decimal (20, 11) ) INSERT INTO #Tmp ( AmountToRelease ,ShareToRelease ,ShareToRelease1stApproach ) VALUES ( 0.823673237, 0.123456781, 100.987654329 ) UPDATE #Tmp SET Dest = AmountToRelease * (ShareToRelease / ShareToRelease1stApproach) select * from #Tmp drop table #Tmp Он дал результат в поле #Tmp.Dest = 0.00100694000. То есть это даже хуже чем было при decimal (18, 9). Почему? И что с этим делать? |
6 окт 17, 14:16 [20848362] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
KellyLynch, SET Dest = AmountToRelease* CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT) |
6 окт 17, 14:20 [20848386] Ответить | Цитировать Сообщить модератору |
Владислав Колосов Member Откуда: Сообщений: 8350 |
KellyLynch, что Вы хотите от компьютера, когда Вы используете дешевый вариант плавающей точки? |
6 окт 17, 14:21 [20848394] Ответить | Цитировать Сообщить модератору |
KellyLynch Member Откуда: Сообщений: 94 |
Да - это сработало. Точность повысилась; спасибо. Теперь бы хотелось объяснения этого: - Почему так происходит? - Где это задокументировано? - На каких версиях SQL Server это происходит? |
||
6 окт 17, 14:29 [20848442] Ответить | Цитировать Сообщить модератору |
KellyLynch Member Откуда: Сообщений: 94 |
Поясните :-) ; не успел за ходом Вашей мысли.... |
||
6 окт 17, 14:30 [20848453] Ответить | Цитировать Сообщить модератору |
Владислав Колосов Member Откуда: Сообщений: 8350 |
KellyLynch, прочтите в справке, что такое DECIMAL и FLOAT, станет понятно. |
6 окт 17, 14:33 [20848474] Ответить | Цитировать Сообщить модератору |
iap Member Откуда: Москва Сообщений: 47052 |
Не уверен, то ли это, что вы хотите. |
||
6 окт 17, 14:36 [20848492] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
ага, оно, забыл как спрашивать :) |
||||
6 окт 17, 14:39 [20848512] Ответить | Цитировать Сообщить модератору |
KellyLynch Member Откуда: Сообщений: 94 |
Спасибо, почитаю. А пока не могли бы Вы ответить на вопрос - а может, вариант ниже дал бы ещё бОльшую точность чем Ваш?: SET Dest = AmountToRelease * (CAST(ShareToRelease as float) / CAST(ShareToRelease1stApproach as float)) |
||||
6 окт 17, 14:47 [20848560] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
KellyLynch, т.е. статью вы не октрыли? |
6 окт 17, 14:51 [20848586] Ответить | Цитировать Сообщить модератору |
KellyLynch Member Откуда: Сообщений: 94 |
Читал, и не один раз :-); просто долго не мог соотнести то что я прочитал – с исходным кодом “SET Dest = AmountToRelease * (ShareToRelease / ShareToRelease1stApproach)” и исправленным “SET Dest = AmountToRelease* CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT)”. Сейчас вроде сообразил; но не вполне уверен. Не могли бы Вы проверить правильность моих рассуждений: 1. Понял – почему увеличение точности моих полей (с decimal (18, 9) до decimal (20, 11) наоборот, приводило к “ухудшению результата вычислений”. Потому что происходила ситуация, о которой говорится в статье как “* Точность и масштаб результата имеют абсолютный максимум, равный 38. Если значение точности превышает 38, то соответствующий масштаб уменьшается, чтобы, по возможности, предотвратить усечение интегральной части результата.” Поэтому например при умножении двух decimal (18, 9) этой ситуации не происходило: “ Точность результата” была p1 + p2 + 1 = 18 + 18 + 1 = 37 что меньше 38. А вот если умножить два decimal (20, 11), то : “ Точность результата” высчитывалась как p1 + p2 + 1 = 20+ 20+ 1 = 41, что больше 38. В этом случае SQL Server делал то что названо “соответствующий масштаб уменьшается”; из-за чего у меня “терялись” числа справа от десятичного разделителя 2. Понял – почему “SET Dest = AmountToRelease* CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT)” стал давать лучший результат. Так как “CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT” есть FLOAT, то и “AmountToRelease* CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT)” стал считаться как произведение двух FLOAT-ов (которое только потом, при записи в поле decimal (20, 11), приводилось к decimal-у). То есть здесь не происходило той “потери”, каковая случалась в пункте 1. Поэтому результат оказывался лучше. 3. А теперь самый сложный вопрос – а есть ли смысл ещё улучшить “SET Dest = AmountToRelease* CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT)”, сделав из него “SET Dest = AmountToRelease * (CAST(ShareToRelease as float) / CAST(ShareToRelease1stApproach as float))”? Мне кажется что есть. Сейчас (“CAST((ShareToRelease / ShareToRelease1stApproach) as FLOAT”) сначала считается деление decimal (20, 11) на decimal (20, 11); и только потом оно преобразуется к FLOAT. “Деление decimal (20, 11) на decimal (20, 11)” ведёт к тому что (как сказано в статье) “Точность результата” определится как p1 - s1 + s2 + max(6, s1 + p2 + 1) = 20 - 11 + 11 + max(6, 11 + 20 + 1) = 52. Это больше чем 38; то есть и тут будет иметь место операция “соответствующий масштаб уменьшается, чтобы, по возможности, предотвратить усечение интегральной части результата”. А если сделать “SET Dest = AmountToRelease * (CAST(ShareToRelease as float) / CAST(ShareToRelease1stApproach as float))”, то сразу будет делиться FLOAT на FLOAT; и операции "соответствующий масштаб уменьшается, чтобы, по возможности, предотвратить усечение интегральной части результата” не случится. Что должно дать “ещё лучший” результат. Ну – какие Вы нашли (если нашли) ошибки в этих рассуждениях? |
||
6 окт 17, 17:16 [20849105] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
KellyLynch, теперь читайте приоритет операци, и приоритет типов. |
6 окт 17, 17:18 [20849109] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
и фиг я вам дам рыбу :) учитесь ловить |
6 окт 17, 17:19 [20849110] Ответить | Цитировать Сообщить модератору |
TaPaK Member Откуда: Kiev Сообщений: 6801 |
и для данного примера достаточно
выше был пример просто из привычки не разбираю всю матиматику |
||
6 окт 17, 17:23 [20849128] Ответить | Цитировать Сообщить модератору |
KellyLynch Member Откуда: Сообщений: 94 |
О – спасибо, это то что надо. В свою очередь я вижу что есть смысл увеличить исходный тип (в моей таблице #Tmp) с decimal (18, 9) даже не до decimal (20, 11) (как я думал сначала), а до decimal (26, 17). Почему не больше? Потому что MSDN https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql говорит “Conversion of float values that use scientific notation to decimal or numeric is restricted to values of precision 17 digits only. Any value with precision higher than 17 rounds to zero.” То есть делать не decimal (26, 17) а decimal (27, 18) уже не будет смысла. Я попробовал, и получил вот что: “Абсолютно точный” результат (посчитанный на Windows Calculator) должен быть “0.823673237 * 0.123456781 / 100.987654329 = 0.00100693542306258884687437406337”. . Результат с использованием Вашего кода “SET Dest = CAST(AmountToRelease as FLOAT) * ShareToRelease /ShareToRelease1stApproach” и с decimal (20, 11) даёт 0.00100693542. Результат с использованием Вашего кода “SET Dest = CAST(AmountToRelease as FLOAT) * ShareToRelease /ShareToRelease1stApproach” и с decimal (26, 17) даёт 0.00100693542306259. Ну – кажется, сделано всё что можно, чтобы получить “наивозможно более точный результат”. |
||||
7 окт 17, 21:58 [20851417] Ответить | Цитировать Сообщить модератору |
Все форумы / Microsoft SQL Server | ![]() |