Pull to refresh

Comments 108

UFO just landed and posted this here
Нам, если не путаю, в институте на курсе фортрана преподавали, что вещественные числа сравнивать через оператор равенства некошерно. По мне главный вопрос, почему в языках программирования по сей день нет штатного оператора для сравнения с заданной точностью.
UFO just landed and posted this here
1. если грубо то да. Проблемы начинаются ещё на этапе преобразования десятичных чисел в двоичные. Например 0.2 + 0.2 === 0.4 таки возвращает true.
2. равенство для большинства практических задач с вещественными числами равенство должно быть приблизительное, но используется как раз точное равенство.
3. мне сравнения с эпсилон в в долях от целого вполне хватило бы ). Либо явного запрета на сравнение оператором ==, да хоть вы warning показывали.
UFO just landed and posted this here
В шарпе, например, штатно присутствует тип decimal, не совсем то о чём Вы говорите, но таких сюрпризов не выбрасывает.
Это тип с фиксированной точностью, прежде всего для финансовых расчётов и подобного. Хотя и для них может потребоваться сравнение с заданной точностью.
Это, это прежде всего, тип для представления именно десятичных значений. Т.е. 0,2 это именно 0.2 так же как 0.3, это именно 0.3.
Т.е. bool eq = 0.2m + 0.3m == 0.5m;
eq будет true
Но ошибок округления конечно и здесь не избежать, но они хотя бы понятны и логичны даже для школьной программы.
decimal a = 1m / 3m;
bool eq = a * 3m == 1m;
Здесь eq уже false и даже простой старшеклассник далёкий от программирования скажет, ну конечно, ведь a * 3 будет 0.99999999
Хотя, стандартный калькулятор у меня в телефоне (андроид) и на винде говорит, что а * 3 всё же будет 1. Так что, не всё ещё потеряно.
Калькуляторы используют небольшой трюк: не показывают последние две-три цифры результата, округляя число при отображении. Если вычислить (1/3-0.333)*3, результат уже будет состоять из девяток.
Больше похоже, что калькулятор где-то в памяти держит не результат 1/3, а именно действие. И тогда у него всё сходится 1/3*3 = 1 пусть я и делаю это в два действия.
Потому что если мы разделив 1/3 скопируем значение в буфер, сделаем сброс в калькуляторе, вставим значение обратно и умножим на 3, то уже получится ноль с девятками.

Вопрос — почему такого оператора нет в Фортране, который специально для математических расчётов создавался.
В мейнстримных языках такого оператора нет по простой причине — они создавались не под работу с плавающей точкой. А в NumPy, R, Julia — есть стандартные процедуры для проверки "примерного равенства".
Впрочем, если нужно "равенство" чисел с плавающей точкой — это уже сигнал того, что они, возможно, используются не по назначению.

Может быть, потому, что операция "приблизительного сравнения" — тернарная: два компаранда и точность. Фортран же не может сам за автора решить, с какой точностью найдены аргументы — это ж надо анализировать и предметную область, и вычислительные методы.


(То, что последний бит мантиссы практически наверняка опорочен — мы можем утверждать. А предпоследний? А сумма двух чисел одного порядка с битым последним битом — удваивает погрешность, и вот уже два бита биты. Кто это будет анализировать, компилятор фортрана?)


А тернарные операции плохо ложатся в инфиксную форму. Проще оформить в виде функции или бойлерплейтной формулы (abs(x-y)<eps — и так стопятьсот раз в коде).


Что до утверждения "если используется равенство флотов..." — жизнь всякие обстоятельства подбрасывает. Можно проверять, если гарантировать, что числа получены одним и тем же способом. Например, что какое-то кешированное значение не изменилось; или что число было прочитано из конфига (и там никакой отморозок не догадался напихать 11 нулей после запятой).

Ладно с ним с Фортраном, язык в лохматом году и, вроде, для прикладных математиков писался, они, в норме, в курсе этой особенности.

Но в тот же js можно фукнцию вроде `Math.floatCompareWithEpsilon(a, b, eps)` в стандарт включить. Глянул, в питоне тоже стандартного способа сравнения нет. В ruby нет, в java и C тоже нет. При этом в языках зачастую есть тернарный условный оператор, который хорош настолько, что им рекомендуют не пользоваться.

Моя фантазия подсказывает что нибудь вроде: `a ~~ (b, eps)`. И реализацию типа `abs(a — b) < max(abs(a), abs(b)) * eps`.

Что не так с тернарным оператором в С/С++, что им не рекомендуется пользоваться?
Кто не рекомендует?

Всё так, пока у нас один такой оператор в выражении, и его операнды компактные. Как только операнды большие, оператор становится нечитабельным. Про вложенность вообще молчу.

А if, значит, с большими операндами — читабельным остаётся?
Аналогично ведь и про него можно сказать.

Многострочность, скобки, отступы (табами) и выравнивание (пробелами) никто не отменял.

У Фортрана есть стандарты Fortran77, Fortran90, Fortran95, Fortran2003, Fortran2007 и актуальный Fortran2018. За столько ревизий можно было и добавить, для неофитов.


В js как раз не надо, пусть страдают.

Ну фортран вообще прикольный был. Поэлементные операции с массивами, оператор вывода сразу таблицы с зубодробительным (как мне тогда казалось) синтаксисом. Плюс привязка синтаксиса к перфокартам.

А в NumPy, R, Julia — есть стандартные процедуры для проверки «примерного равенства».

Можно попросить вас поделиться синтаксисом?

Numpy: numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False) (скаляр -> скаляр, массив -> массив), numpy.allclose() (массив -> скаляр)
R: almost.equal(x, y), all.equal(x, y)
Julia: x ≈ y, isapprox(x, y; rtol, atol) (x .≈ y или isapprox.(x, y; rtol, atol) для векторизованной версии)

Ой, в питоне я с этим столкнулся, второй раз в жизни что-то попытавшись на нем написать. Вот в этой статье habr.com/ru/post/442602 у меня были значения времени светофора с точностью до секунды, а при расчетах скорости и времени машин в пути получались уже дробные значения. Помучился и тупо объявил float вообще везде.

Банан порадовал, радикс заинтересовал, масссивы не удивили, а ошибки в числах с плавающей запятой знакомы уже почитай 25 лет.

Почему именно 25 лет?
А раньше было всё нормально? Что произошло 25 лет назад?
Может он стал программистом 25 лет назад?

Примерно 25 лет назад я первый раз попытался сравнить два значения типа real на равенство, облажался и узнал у препода, почему так нельзя. ;)

Тогда неверно.
ошибки в числах с плавающей запятой знакомы уже почитай 5 лет.
Принято говорить, что С++ сложный, муторный, памятью не управляет, и вообще ад. А я тут смотрю на всякие приколы динамической типизации, преобразования null и NaN в строки без explicit-каста, массивы, которые кастятся в строки с дефолтным разделителем и суммируются конкатенацией, и думаю, что моя судьба С++-программиста не так уж и плоха =3
UFO just landed and posted this here
RTFM — полезно, да. Однако, любому программисту периодически приходится болеть — спьяну на работу пришел, кранчил две недели подряд, дома ребенок спать не дает =3 В таких ситуациях своевременная индикация ошибок — дело крайне полезное. А тут все это не то, что на этапе компиляции, даже в рантайме не валится. Вот это, гхм, моя основная претензия к JS, да и ко всем языкам динамической типизации, по сути.

Лол, кто-то заехал ко мне в карму и снял единичку. Джаваскриптофаги негодуют.
Пересадил людей на Typescript
Они используют :any
Заголовок спойлера
image
UFO just landed and posted this here
Гхм, все читаю свое последнее сообщение, пытаясь понять, где ж я сказал, что тесты не нужны. Не получается.

К слову, 100% покрытия тестами не бывает.
Если вы проверяете работу, то ошибки найдёте так и так.


Это да. Но все же предпочтительнее, когда используемый инструмент упрощает эту задачу, а не усложняет, как JS.
UFO just landed and posted this here
Это вы сейчас про какой инструмент говорите?
UFO just landed and posted this here

Научно обоснованных цифр, конечно, не будет.
Но опыт поддержки вот такого "быстро разработанного" лично у меня вызывает боль.
Если продукт не "на полгода, а потом перепишем", то вся эта неявная типизация вылазит боком.
Но это чисто локальный опыт из реального мира продуктовой разработки.

UFO just landed and posted this here

Фанатиком выглядите именно вы.
И чем чаще употреблять выражения типа "говнокод", "обмазываться" — тем больше видна степень именно вашей упоротости.
Как код-ревью поможет если ваш слабо-типизированый код используют другие — непонятно.
Быстрая устранимость ошибок, конечно же, у вас с пруфами, да?

UFO just landed and posted this here

Я начинаю писать и у меня не компилируется, если типы не совпадают.
Без сборки, запуска тестов, деплоя на интеграцию и прочее.
Я вас удивлю, но даже просто IDE в процессе написания проверяет корректность, т.е. "Build" делать не нужно — красненьким подчеркнут заранее а подавляющем большинстве случаев.


Сторонники типизации, как раз, проверяют, что пишут, неявно.
А упорные фанаты слабой типизации — вчитываются в код и верят в тесты.


Какой же результат вам предсказался, не стесняйтесь, расскажите.

UFO just landed and posted this here

При чём тут ваши локальные проблемы с ТС?
Еще раз повторюсь, я не компилирую, мне IDE на лету анализирует, перед билдом.


Вы притворяетесь, что не понимаете, что сильная типизация не отменяет тесты и контроль качества.
А вот слабая перекладывает ответственность на последующие шаги, такие как — тесты, интергация, QA и т.п.
Зато быстро собирается (хотя сборка — это мизер во времени разработки).

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

Отличное предположение, знаковое я бы сказал.
У вас какой-то пунктик на скорости разработки? Сколько символов в минуту нужно генерировать, чтобы не оказаться "обмазаным"?

JS по своей природе создан для примитивной разработки

Fixed. Выпадающие менюшки, картинки подгрузить, вызовы в апплеты прокинуть. Никакого рокет-сайнс, чисто чтобы с этой задачей могли справиться даже дизайнеры. Оттуда и решения вроде неявного приведения типов, которое иначе могло бы несчастных дизайнеров просто запугать (вы их еще вручную памятью управлять заставьте, ага!).

А для быстрой разработки подойдет абсолютно любой ЯВУ (вписывающийся в ограничения задачи, разумеется). При одном лишь условии — если он хорошо знаком. Потому что к новому инструменту, даже если он по итогу эффективнее, все равно нужно время на привыкание. А если нужно быстро — этого времени, как правило, нет.

Я не могу сравнивать TS и JS, за неимением достаточного опыта работы с обоими языками, но мои предпочтения все равно будут на стороне сильной статической типизации: тесты и ревью это хорошо, но цикл обратной связи получается уж больно длинным, да и опечатка все равно может через них прорваться и наломать дров. При этом компилятор и статический анализатор вполне могут отловить ее еще на стадии написания кода.
TS проверяет типы за тебя, помогает с автокомплитом, служит маленькой документацией к коду, добавляет немного сахара.
Позволяет опираться на типы где они есть, и в то же время позволяет не упарываться с типизацией по полной, можно игнорировать, если нужно. Идеально.
Минусов по сравнению с JS никаких, если не быть фанатиком и не выставлять флаги самых строгих проверок.
Хотя и в нем хватает не совсем очевидных мест, и иногда сюрпризы выкидывает.

Спасибо, буду знать что выбрать, если нелегкая занесет во фронтенд :) и если не удастся пролоббировать использование Blazor %)

Мы все держим за вас кулачки. Вебасм в президенты!

Рекомендую взглянуть либо в сторону функциональных языков(haskell, ocaml) либо в сторону crystal. Они статически типизировны, но в них типизация хорошо проработана и указывать типы там требуется редко.
UFO just landed and posted this here

Программа, как раз, не работающая, а, в лучшем случае, скомпилировавшаяся.
Не путайте тёплое с мягким.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Вы постоянно пишете какие-то цифры, но не приводите их источники.
Откуда 5%? Почему не 10% или 30%?
Что есть настоящие ошибки?
Вы уверены в своих тестах? На каком основании? Покрытие — ещё не показатель, если что.

Кстати да. Тесты тоже могут содержать ошибки, как бы странно это ни звучало для некоторых.
А кто сказал, что в случае по ссылке — ошибка кодирования? Не было требования, ограничивающего скорость выполнения на большом массиве данных, и/или требования, ограничивающего приоритет выполнения задачи — получите, распишитесь. Задача формально решена, проходит тесты и ревью, а то, что у кого-то из пользователей из-за этого могут возникнуть неудобства — так это в следствие незапланированного режима эксплуатации. В одном из следующих апдейтов предусмотрим работу в вашем режиме, если вы настолько важный клиент.

Верность алгоритма покажет только эксплуатация. (Ну или тестирование модели, но на этом этапе именно кода еще не написано ни строчки)
Корректность реализации алгоритма покажут тесты.
Корректность записи алгоритма покажет компилятор/анализатор.

Ну а лапшичность кода — это вообще не ошибка, а дело вкуса и стиля, принятого в команде. Может, там одни пастафарианцы работают, кто их знает?
UFO just landed and posted this here

«корректность реализации алгоритма сортировки» = соответствие написанного кода спецификации алгоритма. Т.е. что при одинаковом вводе и выполнении алгоритма "по бумажке" и в бинарном виде будет получен одинаковый результат. Именно это и проверяют тестами по определению, нет?

UFO just landed and posted this here
Если в спецификации будет черным по белому написано, что алгоритм состоит из цепочки «если-то», и такая спецификация каким-то чудом пройдет до этапа кодирования, то да: такая реализация будет корректным отображением документа в код и это будет подтверждено тестами.
Если же в спеке написано одно, а прогер с тестером сговорились и воткнули костыль на ифах с определением лишь тех входных значений, которые будут проверяться в тестах, то с формальной точки зрения реализация будет признана корректной и уйдет в прод. Правда, потом кто-то из пользователей обязательно столкнется с не протестированным сценарием и крайним окажется тестировщик, потому что плохо составил тест-план и пропустил ошибку, но это уже, как говорится, совсем другая история.

Ну и наконец встречный вопрос: а к чему Вы клоните? Что тесты могут быть несовершенны и иметь, например, дырявое покрытие? Так я с этим не спорю. Если я где-то был по Вашему мнению не прав — прошу указать.
UFO just landed and posted this here
Ну да. Мне определенно стоило начинать свои тезисы с приставки «не-»: неверность, некорректность и т.п. Как всегда спешу и не задействую мозг на полную.

За ссылку спасибо. Возможность доказать корректность программы математически давно подкупает в ФП, но разобраться детально пока не выходило. Видимо, пришла пора.

А в спеке на алгоритм написано, как именно алгоритм должен быть реализован? Чем тогда спека принципиально отличается от реализации алгоритма, кроме чуточку менее машиннопонимаемого языка?

Иногда бывает нужно реализовать не алгоритм сортировки вообще, а вполне конкретный — например, все ту же сортировку слиянием. В этом случае к спеке прикладывают описание алгоритма в виде блок-схемы, псевдо-кода, кода на другом ЯП и т.п. в качестве reference implementation, а задача программиста сводится к трансляции этого описания в код на языке, используемом в проекте. Например, потому что математик, проработавший алгоритм, знает только питон, а в проекте для подобных числодробилок повелось использовать плюсы.
Безусловно, речь идет о частном случае. В общем случае ни в какие детали реализации спека лезть не должна.

Такую — никак. Зато чудесно поймает ошибки при рефакторинге. Или вы пишете идеальную архитектуру с первого раза?

Интересный подход!
Типизация не ловит косячные алгоритмы (если только это не какой-нибудь идрис), поэтому не будем обмазываться типами, не будем обмазываться ассертами, лучше будем обмазываться всеядной динамикой и скрещивать пальцы, что оно как-то работает.
Всё равно умрём, давайте умрём от наркотиков, хотя бы первое время будет весело.


Юнит-тесты тоже не нужны, потому что тотальное покрытие юнит-тестами возможно только для очень простых юнитов. А всё сколько-нибудь сложное — это, на самом деле, или интеграционные тесты (мы хотим убедиться, что для уже известного нам хорошего сценария всё пойдёт хорошо), или регрессионные (хотим убедиться, что для известного плохого сценария всё было плохо, но мы исправили баг и стало хорошо). А все неизвестные нам сценарии остаются не покрыты, и мы подождём, пока не бахнет в продакшене.
Ну что, убедил вас?


Если есть какой-то механизм верификации кода, то лучше им пользоваться, чем не пользоваться. Хоть тестами, хоть линтерами, хоть чем угодно. И типизацией тоже.
Вот вам часто надо писать на жабаскрипе код принципиально полиморфный, чтобы там суммировались числа, строки, наны и массивы без разбору? Или, всё же, есть некая авторская задумка, какая переменная какие значения должна принимать — просто вам лень делать разметку, "и так всё понятно"?

UFO just landed and posted this here

Повторю вопрос.
Откуда вы взяли, что ваши тесты правильные?
Вы видели пример с бананом? Код-ревью поможет разобраться, что один пробел влияет на поведение всех этих неявных преобразовний?

Вот именно: вы фанатично считаете, что статический анализ кода (включая строгую типизацию) не нужен, он не спасает от алгоритмических ошибок, тесты наше всё.


Но тесты не покрывают код! А раз они не покрывают код, значит, что? Правильно.
Или покажите юнит-тест, который найдёт в вашем коде все бананы.


Да и код-ревью легко пролюбить в глаза с одним лишним пробелом. Статический анализатор мог бы найти этот опасный код по формальным признакам, но вы от него отказались.

Тесты писать, кстати, тоже нужно с умом.
Есть вещи, которые тестами экономически невыгодно проверять, т.к. сами тесты будут сложнее тестируемого.
Да, такое не случается в мире правильных продуктов, но не всем повезло.
ТС — это еще совсем-совсем не хардкорная типизация и тратится на такого рода вещи очень мало времени.

Просто все бояться спьяну отстрелить себе конечность)
UFO just landed and posted this here

Это проблемы слабой, а не динамической типизации.


Пример на C:


printf(3 + "abcdefg"); # stdout: defg

(да, я знаю, что стринга, это указатель в C)

Да, если нужно выстрелить в ногу — тут без C никак ;)
Но на эту конструкцию хоть warning у некоторых компиляторов есть.

Честно говоря не уверен, что мой пример с С правильный (не успел удалить).
В "строгом" Delphi есть тип PChar (Pointer to char) к которому можно добавить число, но это не мешает ему быть "строгим". Так что, это больше похоже на переопределение методов как в "строгом" Python с "abc" * 3.


Кто-то может привести лучший вариант, почему в С официально слабая типизация? (примеры неявного конвертирования)

Правильный пример ;)
clang выдает warning.
gcc даже предупреждений не выдает. Логично, int + char* == char* + int == char* со смещением. В каждом любом проекте на C эта конструкция присутствует.

почему в С официально слабая типизация?

Так на этом построена реализация массивов через указатели в С. И вся сишная магия указателей.
Боже пусть подобные хаки будут только в твитере, а не в продуктивном коде
+1
Сейчас же модно «собирать» CSS и JavaScript.
Вот в инструментах сборки и сделать такие codestyle-правила, чтоб не попадало на прод.
UFO just landed and posted this here
Арифметические операции с float/double имеют такую особенность в любом языке. Я ради любопытства на собеседовании спрашиваю, как люди работают с суммами (если человек явно указывают разработку чего либо имеющего отношение к финансам), дак вот почти никто ничего сказать не может, и это пугает, черт побери.
UFO just landed and posted this here
UFO just landed and posted this here
Для начала неплохо бы знать, что будут проблемы. А потом можно и bcmath или *100 или округление. К тому же не правилами округления едиными.
UFO just landed and posted this here
Судя по задачам, я, выходит, матерый жабаскриптер в квадрате :) А всего то 13 лет пишу на нем :D На самом деле представленные кейсы весьма простые, в интернетах можно найти и более не очевидные задачки.
Я регулярно собеседую людей, у которых опыт JS года два и которые хотят его преподавать.
Вот завидую самоуверенности некоторых :D Хотя может там 2 года именно коммерческой разработки, а не кодинга, а суммарно, скажем, 4 года, или просто гении по жизни :) Вообще вполне логично- сейчас 3-4 месяца курсов и джун, год и мидл, 2- уже сеньйор, пора делится мудростью)
Не понимаю как можно хотеть преподавать и зачем это им, как по мне, для разраба это скучнейшее и совсем невыгодное занятие, к тому же большинство из нас около-интроверты и преподавание выматывает.
Вспоминая университетскую практику, где к преподаванию младшим курсам старшекурсники вполне привлекались, а уже для аспирантов педнагрузка была просто обязательной…
UFO just landed and posted this here
Педнагрузка включает в себя не только лекции, семинары вполне покатят.
Ну и да, в разных ВУЗах теоретически могло быть по-разному.
Банан не работает. Так работает:
let a='a';('b'+'a'+ ++a+'a').toLowerCase();
Убрать переменную — не выйдет, убрать пробел — тоже. А так получается очевидно.
>>('b'+'a'+ +'a'+'a').toLowerCase()
>>"banana"

За кавычками попробуйте проследить, за количеством "+" и не забудьте про пробел между двумя "+"
Оу. Не заметил пробела между ними. Ваша правда.
На скриншоте просто выглядит как два плюса подряд.
Вот этого: ('b'+'a'+ +'a'+'a').toLowerCase() достаточно — на выходе будет «banana»
Между двумя плюсами пробел действительно обязателен.
Баян, на хабре уже как минимум 2 статьи про эти же задачки.
В первом примере не сразу дошло, что между плюсами есть пробел. Забавно. Остальные примеры — довольно банальны для всех, кто знаком с JS.
Sign up to leave a comment.

Articles

Change theme settings