Возник очень любопытный и очень неприятный эффект. Суть в следующем. Запускаю один единственный скрипт, открыт один график, открыт один источник данных. Сессия закрыта, помех связи нет. Дальше самое интересное. Есть функция, назовем ее func() такого вида:
local function func() local x, y, z, param -- Присваиваются значения переменным y, z, param. Все значения - целочисленные (integer). x = (y-z)*param -- результат в x тоже целочисленный -- некоторые действия, которые не изменяют значений всех четырех переменных -- Вторично присваиваются значения переменным y, z, param. Все значения - целочисленные (integer). x = (y-z)*param -- снова результат в x тоже целочисленный -- некоторые действия, которые не изменяют значений всех четырех переменных -- Снова (уже в третий раз) присваиваются значения переменным y, z, param. Все значения - целочисленные (integer). x = (y-z)*param -- результат в x оказывается числом с плавающей точкой (double). Ошибка в расчетах! end
Эффект проявился впервые. В документации по Lua сказано, что интерпретатор знает, какой должен быть тип у результата арифметической операции, в зависимости от операнда. Здесь же получается, что все операнды - целочисленные, но результат или получается или остается типа double. Сооружать костыли в виде постоянного использования math.floor как-то не хочется. Есть ли у кого помимо разработчиков какие-то соображения, почему такое могло случиться? Были ли у кого подобные "фокусы".
PS. Была бы в Lua четкая типизация данных, такого эффекта не могло бы быть в принципе. Жаль, что все идет в виде union.
Михаил, это понятно. Относительно операций, где присутствует, хотя бы теоретическая возможность генерации результата типа double, у меня нет никаких вопросов. Но в описании Lua, в частности, самого Р.Иерусалимски, указано, что условно для пользователя тип конкретной переменной определяется значением, присваиваемым "на лету". Т.е., пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001). Но для пользователя это трактуется как 3. И если я проведу сравнение (x == 3), то получу значение true, а не false, как было бы в случае с чистым double/ Если исходить из того, что "условно целочисленное представление" не работает в большинстве случаев с присваиванием чистых значений integer, то тогда вся арифметика, мобильность и логические отношения в Lua летят вверх тормашками.
У меня есть очень сильное подозрение, по аналогии с целым рядом других ситуаций, что эффект, подобный описанному мною, получается из-за ориентированности терминала QUIK на нормальную работу скриптов небольшого размера. Возможно, проблема в накоплении каких-то погрешностей, где-то, может быть, запоздал вызов сборшика мусора. Вобщем, что-то тут есть. По моим исследованиям, как только скрипт (основной плюс подключаемые через dofile), превышает суммарно некоторый объем, при выполнении скрипта начинаются какие-то совершенно несуразные ошибки.
В этой связи, хочу еще спросить у разработчиков: какой суммарный объем скрипта в килобайтах обеспечивает безошибочную работу в рамках терминала QUIK? То, что есть предел определенный - несомненно. Хотелось бы его узнать поточнее. А также еще вопрос: с какой периодичностью при выполнении скрипта QUIKв фоновом режиме запускает сборщик мусора?
арифметика плюс/минус между целыми числами, хранчщимтся в формате double, в 99.99 случаях даст целое, если сумма не представится большим числом, в котором будет переполнение мантиссы.
умножение и деление, не говоря уже о всяких корнях, в большинстве случаев уже даст накопившуюся ошибку.
Andrei2016 написал:
Т.е., пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001). Но для пользователя это трактуется как 3. И если я проведу сравнение (x == 3), то получу значение true, а не false, как было бы в случае с чистым double/
Из того, что вы пишите, видно, что вы заблуждаетесь (про связь размера скрипта и точности вычислений - вообще нонсенс). Но чтобы вам это объяснить нужно, чтобы вы сначала показали конктерные цифры, с которыми вы оперировали, и как именно вы определяли, что в последнем x - double, а не "integer".
^ что-то пошло не так (видимо, слеш в конце цитаты), жалко, что нет возможности редактировать посты.
Цитата
s_mike@rambler.ru написал: умножение и деление, не говоря уже о всяких корнях, в большинстве случаев уже даст накопившуюся ошибку.
Умножение целого на целое ошибки не даст (опять же, если результат влезает в +-2^54, как вы правильно заметили). Я думаю, что Andrei2016 просто ошибается либо в том, что на входе в x = (y-z)*param у него целые, либо в том, что на выходе не целое. Это вообще типично: приходит человек, и делает утверждение, "Я написал скрипт, и абсолютно уверен, что он правильный. А результат - неверный! Скажите, это что не работает: Lua, Quik, или компьютер?"
Цитата
Andrei2016 написал: По моим исследованиям, как только скрипт (основной плюс подключаемые через dofile), превышает суммарно некоторый объем, при выполнении скрипта начинаются какие-то совершенно несуразные ошибки.
В этой связи, хочу еще спросить у разработчиков: какой суммарный объем скрипта в килобайтах обеспечивает безошибочную работу в рамках терминала QUIK? То, что есть предел определенный - несомненно.
Предлагаю рассмотреть другую гипотизу: вы пишите небольшой скрипт, делаете небольшое количество ошибок, все более-менее работает. Вы пишите большой скрипт, делаете большое количество ошибок - все перестает работать. Уверенность в отсутствии ошибок в собственном коде - основная помеха их исправлению...
kroki, проверяется элементарно через message("x= "..x.." y= "..y.." z= "..z.." param= "..param). Можете убедиться сами. Речь идет не о сложном или простом коде. Речь идет о том, что происходит одно и то же присваивание результата от оперирования целочисленными значениями три раза: дважды результат - целочисленный, а на третий раз - с плавающей точкой. Вопрос: каким образом это происходит? Повторюсь: нет никаких сторонних операндов или странных преобразований.
Относительно повышения ошибочности скрипта при превышении определенного объема. К сожалению, не правы вы. Самая простая вещь: присвоение значения элементу таблицы и затем адресация при скрипте , скажем в 30К работает как и должно. При скрипте объемом 300К вы начинаете получать ошибку ровно в том же самом месте. Я это уже проходил, и даже писал об этом на форуме. При этом заметьте: я веду речь не просто о stand-alone Lua, а именно о трансляции скрипта под управлением QUIK.
Проверил. При переводе числа в строку Lua по умолчанию округляет до 14 знаков (ну и отбрасывает хвостовые нули, формат "%.14g"). Однако в double знаков, имеющих значение - 17. Поэтому то, что в выдаче message() вы видите как "целое" число, на самом деле таковым может и не являться. Можете поверить:
Код
local x = 1.000000000000007
if x ~= 1 then
message("x="..x..", real x="..string.format("%.17g", x))
end
Сообщение будет выведено, ибо x не равен 1. Но в самом сообщении в "элементарной" его части будет написано, что равен (в правильной - реальное значение, которое не равно и исходному, ибо оно не представимо точно). Вооружившись этим знанием, перепроверьте все ваши скрипты по 300K - возможно, найдете и другие заблуждения, и вторая проблема тоже уйдет ;) (я с ней помочь не смогу (думаю, что и другие тоже), но стою на своем: ошибка у вас, не в Lua - и вам только кажется, что факт запуска Lua под Quik что-то меняет в плане корректности выполнения).
kroki, проблема в том, что эффект проявился на 11-м знаке, а не на 17-м: вместо 2 message выдал 1.999999999991. Это первое. Второе. Я же четко написал: присваивается операнду ЦЕЛОЧИСЛЕННОЕ значение, т.е. условно "x=10". Вариант, который вы приводите в качестве аргумента - "local x = 1.000000000000007", изначально содержит число с плавающей точкой, а не целочисленное. Посему к моему случаю ваш пример отношения не имеет. Относительно остального: речь в принципе идет не о логике вычислений и не об ошибках алгоритма, а о результатах одной и той же простейшей операции с технической точки зрения. Мои опыты показали, при падении объема скрипта до уровня примерно 50К-60К (просто за счет физического убирания нескольких процедур, при оставлении неизменным содержимого сбойной операции), все становится идеально. То, что QLua в плане своей "аппаратной" (назовем так) реализации существенно отличается от того же Lua for Windows, - это уже и проверенный, и доказанный не только мною факт. Зачем опровергать очеивдное?
Andrei2016 написал: kroki, проблема в том, что эффект проявился на 11-м знаке, а не на 17-м: вместо 2 message выдал 1.999999999991. Это первое. Второе. Я же четко написал: присваивается операнду ЦЕЛОЧИСЛЕННОЕ значение, т.е. условно "x=10".
Выложите сюда фрагмент кода с ошибкой, чтобы мы смогли его запустить у себя на Quik и воссоздать ситуацию. Тогда быстрее поможем найти ошибку...
Andrei2016 написал: проблема в том, что эффект проявился на 11-м знаке, а не на 17-м: вместо 2 message выдал 1.999999999991. Это первое.
Происходит следующее: у вас после 14-го знака есть дробная часть, но вы своим "элементарным" тестом ее не видите из-за округления. После умножения на param эта дробная часть передвигается в 11-й знак, вы начинаете ее видеть и дивиться.
Цитата
Второе. Я же четко написал: присваивается операнду ЦЕЛОЧИСЛЕННОЕ значение, т.е. условно "x=10".
Нет, вы вполне "четко" написали, что вы думаете, что присваивается целое, а на вопрос, почему вы так думаете, вы показали неверную проверку. Вы думаете, что
Цитата
Andrei2016 написал: Т.е., пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001). Но для пользователя это трактуется как 3.
а это вообще нонсенс. Фактически вы и утверждаете, что в вашей терминологии 3,000000000001 - целое. Поэтому не нужно никаких "условно x=10", прямо перед строкой x = (y-z)*param сделайте присвоение своих целых значений всем переменным, и убедитесь, что результат - тоже целое. Или проверьте, что y == math.floor(y) (и также для z и param). Ну или покажите эти числа, как вам уже дважды предложили. Это, конечно, если вы хотите решить свою проблему, а не продолжать заблуждаться, будто компьютер вас обманывает...
Andrei2016 написал: Мои опыты показали, при падении объема скрипта до уровня примерно 50К-60К (просто за счет физического убирания нескольких процедур, при оставлении неизменным содержимого сбойной операции), все становится идеально. То, что QLua в плане своей "аппаратной" (назовем так) реализации существенно отличается от того же Lua for Windows, - это уже и проверенный, и доказанный не только мною факт. Зачем опровергать очеивдное?
Ммм, хотя что тут можно сказать? В МИД Англии или США вас ждет прекрасное будушее :). На наших же просторах доказательства нужно приводить, а не только утверждать, что они у вас есть. Есть что-то там "очевидно" - пожалуйста, покажите.
kroki, почитайте на досуге товарища Р.Иерусалимски - создателя Lua - относительно типизации значений и переменных в Lua. И тогда все ваши рассуждения по поводу 14, 17 и 19-го знаков пойдут лесом. Вам русским языком говорят: ПРИСВАИВАЕТСЯ ЦЕЛОЧИСЛЕННОЕ значение. Понимаете: ПРИСВАИВАЕТСЯ, а не предполагается. Если вам это непонятно, то говорить не о чем. По поводу очевидных доказательств. Вы предлагаете мне запостить на форуме порядка 200К программного кода? Можете заняться этим сами, если ARQA позволит. Кусок кода, где проявился эффект я привел. Можете искать очевидное или невероятное сколько влезет. Меня же интересовало мнение других, сталкивался кто-либо с таким эффектом или нет. Кто смог сказать что-то по делу, сказал.
Andrei2016 написал: kroki, проблема в том, что эффект проявился на 11-м знаке, а не на 17-м: вместо 2 message выдал 1.999999999991. Это первое. Второе. Я же четко написал: присваивается операнду ЦЕЛОЧИСЛЕННОЕ значение, т.е. условно "x=10".
Выложите сюда фрагмент кода с ошибкой, чтобы мы смогли его запустить у себя на Quik и воссоздать ситуацию. Тогда быстрее поможем найти ошибку...
Suntor, так я привел его в своем первом сообщении. В том-то и дело, что ничего подобного такому эффекту быть не должно. Поэтомуречь и идет не об ошибке, а об эффекте. Меня же интересовал вопрос, из-за чего может проявиться такой эффект: дважды после присаивания значения integer все происходит идеально, на третий раз - результат не целочисленный, а с плавающей точкой. Как я понял, ни у кого подобный эффект не проявлялся. Ну. на нет - и суда нет. Кстати, "подпорку" для решения вопроса я уже соорудил. Вы будете долго смеяться, когда узнаете как. :) Решение оказалось не менее странным, чем сам эффект: Перед третьим присваиванием "x = (y-z)*param" нужно добавить еще одно присваивание, скажем так: "x=10". Все - на этом эффект числа с плавающей точкой в x исчезает, и message выводит нормальное 2. А теперь, если вы уберете "подпорку", эффект double возвращается на место.
Andrei2016 написал: Suntor, так я привел его в своем первом сообщении.
Это не код... это его схема с комментариями... так ошибку не поймать.
Эту функцию вашу, где происходит глюк в математике, вынесите в отдельный .lua файл... отрежьте всё лишнее, чтобы остались только те несколько строчек с вычислениями где глючит... но код должен быть рабочий со значениями констант, чтобы его можно было запустить и воспроизвести эффект... после этого его сюда киньте, и мы посмотрим в отладчиках и по коду, что там реально происходит... чтобы найти причину
Цитата
Andrei2016 написал: Решение оказалось не менее странным, чем сам эффект: Перед третьим присваиванием "x = (y-z)*param" нужно добавить еще одно присваивание, скажем так: "x=10". Все - на этом эффект числа с плавающей точкой в x исчезает, и message выводит нормальное 2. А теперь, если вы уберете "подпорку", эффект double возвращается на место.
Никогда так не делайте... вы закрыли непонятную ошибку, непонятной заплаткой... проблема осталась и выплывет в будущем в самый неподходящий момент. В результате, можете просто деньги потерять от заглючившего скрипта...
Suntor, и вы туда же. Мне что: больше делать нечего, кроме как моделировать странный эффект? Я вам привел функцию, можете оставить в ней вообще 5 строк и воспроизводить хоть миллион раз. Только, прежде чем соберетесь увидеть этот эффект, увеличьте объем своего программного кода до 200К (без учета объема комментариев) - неважно чем. Вот, тогда и увидите. Это отдельно стоящий, ни с чем несвязанный (ни с какими-либо другими функциями, данными) участок кода. Можете считать его полностью локализованным. Действие "подпорки" в виде "x=10" однозначно говорит о наличии погрешности при заполнении структуры, отвечающей за идентификацию переменной в Lua. Вопрос в том, из-за чего происходит эта погрешность. Один из возможных вариантов: фоновый вызов сборщика мусора с недостаточной для имеющихся условий частотой. Почему, собственно я и просил разработчиков указать заложенную в текущей реализации терминала частоту вызова сборщика мусора при работающем пользовательском скрипте.
Andrei2016 написал: Suntor, и вы туда же. Мне что: больше делать нечего, кроме как моделировать странный эффект? Я вам привел функцию, можете оставить в ней вообще 5 строк и воспроизводить хоть миллион раз. Только, прежде чем соберетесь увидеть этот эффект, увеличьте объем своего программного кода до 200К
Сделал .lua файл с таким содержанием:
Код
local function func()
local x, y, z, param
y = 10
z = 5
param = 2
x = (y-z)*param
message("x = "..x)
x = (y-z)*param
message("x = "..x)
x = (y-z)*param
message("x = "..x)
end
func()
Запустил у себя и получил чётко три сообщения с текстом: x = 10 x = 10 x = 10 Ничего удивительно.
Теперь, можете скопировать эту функцию (НЕ МЕНЯЯ В НЕЙ НИ ОДНОЙ СТРОЧКИ КОДА!) внутрь своего «большого» 200К скрипта, и посмотреть вывод сообщений. Я уже знаю в общем-то что у вас получится, но нужно, чтобы вы сами в этом убедились...
Andrei2016 написал: x = (y-z)*param -- результат в x оказывается числом с плавающей точкой (double). Ошибка в расчетах!
Приведите, пожалуйста, конкретные значения y, z и param, которые дают x - double. Где-то выше обсуждалось, что 1.9999.... <> 2, но это здесь ни при чем, т.к. в вашем "целочисленном" умножении невозможно получить x=2 (кроме как в 2*1, но здесь не бывает ошибки)
Кроме того,
Цитата
Andrei2016 написал: пишу я, скажем, x = 3. Да, по факту оно хранится как double (допустим, как 3,000000000001)
НЕТ! По факту оно хранится как 01000000 00001000 00000000 00000000 00000000 00000000 00000000 00000000, т.е ровно 3, и никак иначе! Любые целые числа (в допустимом диапазоне) представляется в double с абсолютной точностью.
НО, учитывайте, что: 1. При сравнении двух double чисел последние 16 разрядов мантиссы игнорируются, что соответствует 12-13 значащим цифрам в десятичном представлении числа. 2. Перед сложением (вычитанием) двух double чисел их мантиссы выравниваются по экспоненте (порядку), в результате вы получаете ошибку в последнем разряде. У чисел ~2 и ~5 уже экспонента отличается на 1 разряд, т.е при их сложении (вычитании) теоретически может возникнуть ошибка в последнем разряде мантиссы. И ошибка ГАРАНТИРОВАННО возникает, если в мантиссе меньшего из складываемых чисел последние два разряда - не нулевые. На самом деле, при сложении исходно целых чисел ошибка не возникает, а вот при вычитании ошибка ИНОГДА возникает: вы получаете результат на 1 разряд мантиссы меньше, чем должно быть. Т.е., например, вместо 100 (01000000 01011001 00000000 00000000 00000000 00000000 00000000 00000000) Вы можете получить ближайшее меньшее в double арифметике число 99.999999999999985789145284798..... (01000000 01011000 11111111 11111111 11111111 11111111 11111111 11111111). Это все еще 100 с "запасом" в 15 разрядов. При умножении все то же самое. Сделайте подряд более 16 действий с "целыми" double числами и ошибка может накопиться до 16 разряда мантиссы. Результат: вы получаете "нецелое" число в результате.
Алексей написал: 1. При сравнении двух double чисел последние 16 разрядов мантиссы игнорируются, что соответствует 12-13 значащим цифрам в десятичном представлении числа.
Осмелюсь предположить, что вы ошибаетесь. Если мы говорим об архитектуре x86, то раньше, когде не было еще всяких MMX/SSE, в процессоре были только 80-битные регистры, в которых производились вычисления как double, так и long double. И вот для double последние 16 бит игнорировались, чтобы сравнивались только 64 бита. В QLua вроде все работает, как ожидается:
На самом деле, при сложении исходно целых чисел ошибка не возникает, а вот при вычитании ошибка ИНОГДА возникает: вы получаете результат на 1 разряд мантиссы меньше, чем должно быть.
А пример такой есть? Я могу представить ситуацию, если магнитуды чисел существенно отличаются. Но в общем случае не припомню чего-то подобного (но я и не претендую на глубокое понимание вопроса, мог и не знать). Пример, или ссылку на почитать, плиз :).
В любом случае, проблему OP вы не адресуете, он уверен, что на входе целые числа (правда какие - не говорит, код - не показывает). Гипотеза - сборщик мусора подворовывает из результатов вычислений, я так понял (опять же, нам не объясняют как, предлагают просто поверить). Я бы еще мог предположить, что подключается какой-то дикий модуль на C, который плугом вспахивает всю память, но тогда бы все просто взорвалось, а вот вместо 2 получить 1.999999999991 - это почерк прфессионала. Suntor написал пример, да вот беда: результат первого вычисления x = 10 - будет "подпоркой" для второго (при x=10 все работает - доказано!), результат второго опять x = 10 - будет "подпоркой" для третьего, и сборщик мусора ничего не украдет :(. Тут нужно глубже копать, читать Иерусалимски, разбираться, откуда растет утверждение
Цитата
Andrei2016 написал: Была бы в Lua четкая типизация данных, такого эффекта не могло бы быть в принципе. Жаль, что все идет в виде union.
Ну и радоваться, что нам достались версии Quik безо всех этих проблем :).
Алексей написал: 1. При сравнении двух double чисел последние 16 разрядов мантиссы игнорируются, что соответствует 12-13 значащим цифрам в десятичном представлении числа.
Первый раз такое слышу... это вообще откуда такое?... можно ссылочку. Подозреваю, что вы перепутали 64-хбитный double и внутрисопроцессорный 80-ибитный long double. Эти 16 разрядов отбрасываются не от double'а, а от вычисленного внутри сопроцессора long double при возврате обратно из сопроцессора и привидении его к типу double, то-есть 80-16=64. Точность double при сравнении не меняется, там около 17 десятичных цифр получается.
Такой вот простой .lua файл:
Код
local DBL_EPSILON = 2.2204460492503131e-016
local NOT_DBL_EPSILON = DBL_EPSILON/2
local function checkDblEpsilon(v1, v2)
message(string.format("(%.20g == %.20g) == ", v1, v2)..(v1 == v2 and "true" or "false"))
end
checkDblEpsilon(1.0, 1.0+NOT_DBL_EPSILON)
checkDblEpsilon(1.0, 1.0+DBL_EPSILON)