Comments 108
0.2 + 0.2 === 0.4
таки возвращает true.2. равенство для большинства практических задач с вещественными числами равенство должно быть приблизительное, но используется как раз точное равенство.
3. мне сравнения с эпсилон в в долях от целого вполне хватило бы ). Либо явного запрета на сравнение оператором
==
, да хоть вы warning показывали.Т.е. 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 скопируем значение в буфер, сделаем сброс в калькуляторе, вставим значение обратно и умножим на 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`.
Что не так с тернарным оператором в С/С++, что им не рекомендуется пользоваться?
Кто не рекомендует?
У Фортрана есть стандарты Fortran77, Fortran90, Fortran95, Fortran2003, Fortran2007 и актуальный Fortran2018. За столько ревизий можно было и добавить, для неофитов.
В js как раз не надо, пусть страдают.
А в NumPy, R, Julia — есть стандартные процедуры для проверки «примерного равенства».
Можно попросить вас поделиться синтаксисом?
docs.python.org/3/library/math.html#math.isclose
Банан порадовал, радикс заинтересовал, масссивы не удивили, а ошибки в числах с плавающей запятой знакомы уже почитай 25 лет.
А раньше было всё нормально? Что произошло 25 лет назад?
Примерно 25 лет назад я первый раз попытался сравнить два значения типа real на равенство, облажался и узнал у препода, почему так нельзя. ;)
К слову, 100% покрытия тестами не бывает.
Если вы проверяете работу, то ошибки найдёте так и так.
Это да. Но все же предпочтительнее, когда используемый инструмент упрощает эту задачу, а не усложняет, как JS.
Научно обоснованных цифр, конечно, не будет.
Но опыт поддержки вот такого "быстро разработанного" лично у меня вызывает боль.
Если продукт не "на полгода, а потом перепишем", то вся эта неявная типизация вылазит боком.
Но это чисто локальный опыт из реального мира продуктовой разработки.
Фанатиком выглядите именно вы.
И чем чаще употреблять выражения типа "говнокод", "обмазываться" — тем больше видна степень именно вашей упоротости.
Как код-ревью поможет если ваш слабо-типизированый код используют другие — непонятно.
Быстрая устранимость ошибок, конечно же, у вас с пруфами, да?
Я начинаю писать и у меня не компилируется, если типы не совпадают.
Без сборки, запуска тестов, деплоя на интеграцию и прочее.
Я вас удивлю, но даже просто IDE в процессе написания проверяет корректность, т.е. "Build" делать не нужно — красненьким подчеркнут заранее а подавляющем большинстве случаев.
Сторонники типизации, как раз, проверяют, что пишут, неявно.
А упорные фанаты слабой типизации — вчитываются в код и верят в тесты.
Какой же результат вам предсказался, не стесняйтесь, расскажите.
При чём тут ваши локальные проблемы с ТС?
Еще раз повторюсь, я не компилирую, мне IDE на лету анализирует, перед билдом.
Вы притворяетесь, что не понимаете, что сильная типизация не отменяет тесты и контроль качества.
А вот слабая перекладывает ответственность на последующие шаги, такие как — тесты, интергация, QA и т.п.
Зато быстро собирается (хотя сборка — это мизер во времени разработки).
Отличное предположение, знаковое я бы сказал.
У вас какой-то пунктик на скорости разработки? Сколько символов в минуту нужно генерировать, чтобы не оказаться "обмазаным"?
JS по своей природе создан для примитивной разработки
Fixed. Выпадающие менюшки, картинки подгрузить, вызовы в апплеты прокинуть. Никакого рокет-сайнс, чисто чтобы с этой задачей могли справиться даже дизайнеры. Оттуда и решения вроде неявного приведения типов, которое иначе могло бы несчастных дизайнеров просто запугать (вы их еще вручную памятью управлять заставьте, ага!).
А для быстрой разработки подойдет абсолютно любой ЯВУ (вписывающийся в ограничения задачи, разумеется). При одном лишь условии — если он хорошо знаком. Потому что к новому инструменту, даже если он по итогу эффективнее, все равно нужно время на привыкание. А если нужно быстро — этого времени, как правило, нет.
Я не могу сравнивать TS и JS, за неимением достаточного опыта работы с обоими языками, но мои предпочтения все равно будут на стороне сильной статической типизации: тесты и ревью это хорошо, но цикл обратной связи получается уж больно длинным, да и опечатка все равно может через них прорваться и наломать дров. При этом компилятор и статический анализатор вполне могут отловить ее еще на стадии написания кода.
Позволяет опираться на типы где они есть, и в то же время позволяет не упарываться с типизацией по полной, можно игнорировать, если нужно. Идеально.
Минусов по сравнению с JS никаких, если не быть фанатиком и не выставлять флаги самых строгих проверок.
Хотя и в нем хватает не совсем очевидных мест, и иногда сюрпризы выкидывает.
Программа, как раз, не работающая, а, в лучшем случае, скомпилировавшаяся.
Не путайте тёплое с мягким.
Вы постоянно пишете какие-то цифры, но не приводите их источники.
Откуда 5%? Почему не 10% или 30%?
Что есть настоящие ошибки?
Вы уверены в своих тестах? На каком основании? Покрытие — ещё не показатель, если что.
Верность алгоритма покажет только эксплуатация. (Ну или тестирование модели, но на этом этапе именно кода еще не написано ни строчки)
Корректность реализации алгоритма покажут тесты.
Корректность записи алгоритма покажет компилятор/анализатор.
Ну а лапшичность кода — это вообще не ошибка, а дело вкуса и стиля, принятого в команде. Может, там одни пастафарианцы работают, кто их знает?
«корректность реализации алгоритма сортировки» = соответствие написанного кода спецификации алгоритма. Т.е. что при одинаковом вводе и выполнении алгоритма "по бумажке" и в бинарном виде будет получен одинаковый результат. Именно это и проверяют тестами по определению, нет?
Если же в спеке написано одно, а прогер с тестером сговорились и воткнули костыль на ифах с определением лишь тех входных значений, которые будут проверяться в тестах, то с формальной точки зрения реализация будет признана корректной и уйдет в прод. Правда, потом кто-то из пользователей обязательно столкнется с не протестированным сценарием и крайним окажется тестировщик, потому что плохо составил тест-план и пропустил ошибку, но это уже, как говорится, совсем другая история.
Ну и наконец встречный вопрос: а к чему Вы клоните? Что тесты могут быть несовершенны и иметь, например, дырявое покрытие? Так я с этим не спорю. Если я где-то был по Вашему мнению не прав — прошу указать.
За ссылку спасибо. Возможность доказать корректность программы математически давно подкупает в ФП, но разобраться детально пока не выходило. Видимо, пришла пора.
А в спеке на алгоритм написано, как именно алгоритм должен быть реализован? Чем тогда спека принципиально отличается от реализации алгоритма, кроме чуточку менее машиннопонимаемого языка?
Иногда бывает нужно реализовать не алгоритм сортировки вообще, а вполне конкретный — например, все ту же сортировку слиянием. В этом случае к спеке прикладывают описание алгоритма в виде блок-схемы, псевдо-кода, кода на другом ЯП и т.п. в качестве reference implementation, а задача программиста сводится к трансляции этого описания в код на языке, используемом в проекте. Например, потому что математик, проработавший алгоритм, знает только питон, а в проекте для подобных числодробилок повелось использовать плюсы.
Безусловно, речь идет о частном случае. В общем случае ни в какие детали реализации спека лезть не должна.
Такую — никак. Зато чудесно поймает ошибки при рефакторинге. Или вы пишете идеальную архитектуру с первого раза?
Интересный подход!
Типизация не ловит косячные алгоритмы (если только это не какой-нибудь идрис), поэтому не будем обмазываться типами, не будем обмазываться ассертами, лучше будем обмазываться всеядной динамикой и скрещивать пальцы, что оно как-то работает.
Всё равно умрём, давайте умрём от наркотиков, хотя бы первое время будет весело.
Юнит-тесты тоже не нужны, потому что тотальное покрытие юнит-тестами возможно только для очень простых юнитов. А всё сколько-нибудь сложное — это, на самом деле, или интеграционные тесты (мы хотим убедиться, что для уже известного нам хорошего сценария всё пойдёт хорошо), или регрессионные (хотим убедиться, что для известного плохого сценария всё было плохо, но мы исправили баг и стало хорошо). А все неизвестные нам сценарии остаются не покрыты, и мы подождём, пока не бахнет в продакшене.
Ну что, убедил вас?
Если есть какой-то механизм верификации кода, то лучше им пользоваться, чем не пользоваться. Хоть тестами, хоть линтерами, хоть чем угодно. И типизацией тоже.
Вот вам часто надо писать на жабаскрипе код принципиально полиморфный, чтобы там суммировались числа, строки, наны и массивы без разбору? Или, всё же, есть некая авторская задумка, какая переменная какие значения должна принимать — просто вам лень делать разметку, "и так всё понятно"?
Повторю вопрос.
Откуда вы взяли, что ваши тесты правильные?
Вы видели пример с бананом? Код-ревью поможет разобраться, что один пробел влияет на поведение всех этих неявных преобразовний?
Вот именно: вы фанатично считаете, что статический анализ кода (включая строгую типизацию) не нужен, он не спасает от алгоритмических ошибок, тесты наше всё.
Но тесты не покрывают код! А раз они не покрывают код, значит, что? Правильно.
Или покажите юнит-тест, который найдёт в вашем коде все бананы.
Да и код-ревью легко пролюбить в глаза с одним лишним пробелом. Статический анализатор мог бы найти этот опасный код по формальным признакам, но вы от него отказались.
Тесты писать, кстати, тоже нужно с умом.
Есть вещи, которые тестами экономически невыгодно проверять, т.к. сами тесты будут сложнее тестируемого.
Да, такое не случается в мире правильных продуктов, но не всем повезло.
ТС — это еще совсем-совсем не хардкорная типизация и тратится на такого рода вещи очень мало времени.
Это проблемы слабой, а не динамической типизации.
Пример на C:
printf(3 + "abcdefg"); # stdout: defg
(да, я знаю, что стринга, это указатель в C)
Но на эту конструкцию хоть warning у некоторых компиляторов есть.
Честно говоря не уверен, что мой пример с С правильный (не успел удалить).
В "строгом" Delphi есть тип PChar
(Pointer to char) к которому можно добавить число, но это не мешает ему быть "строгим". Так что, это больше похоже на переопределение методов как в "строгом" Python с "abc" * 3
.
Кто-то может привести лучший вариант, почему в С официально слабая типизация? (примеры неявного конвертирования)
clang выдает warning.
gcc даже предупреждений не выдает. Логично, int + char* == char* + int == char* со смещением. В каждом любом проекте на C эта конструкция присутствует.
почему в С официально слабая типизация?
Так на этом построена реализация массивов через указатели в С. И вся сишная магия указателей.
Не понимаю как можно хотеть преподавать и зачем это им, как по мне, для разраба это скучнейшее и совсем невыгодное занятие, к тому же большинство из нас около-интроверты и преподавание выматывает.
let a='a';('b'+'a'+ ++a+'a').toLowerCase();
Убрать переменную — не выйдет, убрать пробел — тоже. А так получается очевидно.
Между двумя плюсами пробел действительно обязателен.
Разбираем WTF задачки в JavaScript