Comments 114
Касаемо же самой простой задачи сравнения, мне кажется любой знакомый с плавающей точкой должен знать о небольшом эпсилон, который нужно использовать при проверке на равенство.
Напоследок, да, понятно, что этими числами не попользуешься в финансах. Но они и не были созданы для этого. У них достаточно узкий круг задач, с которыми они справляются на ура, если разработчики могут справиться с пониманием плавающей точки.
Заметьте, что 10/9 имеет идеальную точность. И всё, что нужно для точности, — это два кусочка информации. Это числитель и знаменатель. С помощью одного значения BigInt мы можем представлять произвольно большие целые числа. Но если мы создадим пару из целых чисел, то сможем представлять произвольно большие или маленькие числа.
Не произвольные числа, а только рациональные.
Число π или корень из 2 все равно не удастся представить с таком виде, даже имея бесконечный объем памяти
Да-да, и добавить туда же функции типа арксинуса, функцию Бесселя… И результат представлять в виде выражения: arcsin(arcsin(1/3)) в виде такой дроби, как вы предложили, вроде не представляется.
Правда, операции с такими числами будут дико ресурсоёмкими, а порой их сравнение произвести не удастся (равенство двух выражений – это, по сути, теорема, доказательство которой может оказаться весьма сложным). Зато абсолютно точно.
Ах да, это уже сделали. Называется "пакет символьной математики". Derive, Mathematica, Mapple. Имеют свою узкую нишу для применения, с "плохими" floating-point числами не сравнить.
А ничего, что иррациональных чисел будет побольше чем рациональных (множество которых счётно, т.е., эквивалентно множесту натуральных чисел)? Их нельзя так представить.
Справедливости ради, и нужны не все вещественные числа, а только те, которые могут получиться в ходе вычислений – а значит, те, которые мы можем представить (например, в виде алгоритма, вычисляющего это число, заканчивающегося за конечное время). Т.е. конечное множество чисел (ну или счётное, если начинать вычисления с натуральных чисел).
Но представление так себе получается, не очень практичное :-)
Плюс, это полностью бесполезно, если будет открыта какая-то новая важная трансцендентная константа: придётся сначала изучить все её свойства, добавить в программу знания об этой константе.
Справедливости ради, для многих иррациональных алгебраических чисел и даже для некоторых трансцендентных имеются способы их представления даже в конечной памяти с заданной (конечной) точностью. Начиная от рядов Тейлора и заканчивая более экзотичными цепными дробями например.
мощность рациональных чисел счетно (их всех можно пронумеровать натуральными числеми), а иррациональных — напротив, не счетно. Это автоматически означает, что организовать их точное представление в конечной памяти нельзя.
Вы так говорите, как будто в конечной памяти можно организовать точное представление элементов счётного множества, да хотя бы натуральных чисел неограниченной величины.
На первом курсе университета преподаватель линейной алгебры очень советовал почитать книжку "Конкретная математика". Могу ошибаться, но там вроде бы чем-то похожим занимаются
Почему только 15 лет? IEEE 754 приняли ещё в 1985м. Это особенность формата чисел с плавающей точкой, который поддерживается железом. Об этом было прекрасно известно ещё до появления Javascript в том же C например. Не бывает бесконечной точности при фиксированном размере. Предложение хоронить язык на таком основании очень смелое. Какие языки тогда останутся, Python?
Вариант сверху — нерабочий. Чтобы я смог в метод передать аргумент — надо вызвать анонимную функцию, которая вызовет этот метод. Почему? Зачем? Это проблема реакта или самого js?
И это только одна из приколюх с которой я столкнулся.
Проблема реакта, потому что такие проблемы должен решать фреймворк. Во vue и angular такого нет.
Потому что this указывает на объект в котором он вызывается, соответственно на элемент <button/> если его не обернуть в функцию. А если обернуть то на объект функции OnClick().
Логично ли это — да.
Очевидно — нет.
Но благодаря динамической природе this вам не нужно прокидывать "контекст".
Ответ на ваш вопрос заключается примерно в этом:
typeof (2 + 2); // number
typeof (() => 2 + 2); // function
рекомендую посмотреть на то, во что компилируется ваш JSX код, чтобы исчезла "магия" и всё стало очень простым и понятным
Поставил минус в карму за быдлятину в примере кода — я не хочу видеть это дерьмо на хабре.
this.changePanel.bind(this, 'second')
Если бы вы не использовали jsx хотя бы на время изучения, вся последовательность «рендера» компонента была бы куда понятнее.
Ruby! Какой глупый язык.
Clojure! Какой глупый язык.
Haskell! Какой глупый язык.
Я не видел ни одного «wtf js?!» с демонстрациями вида «0.1 + 0.2 != 0.3», потому что это довольно общеизвестная проблема среди большинства языков. С этим сталкиваются практически все в любой арифметике (за исключением отдельных математических движков вроде numpy/wolfram-mathematica, которые профессионально умеют вплоть до натуральных дробей), поэтому или принудительно округляют числа до какого-то знака перед прямым сравнением, или сравнивают их исключительно на «больше-меньше».
Некоторые ребята называют JS глупым языком из-за странных автоматических приведений типов, вроде
0 == [] //> true
//но
[] == ![] //> true
// и
10 + "20" //> "1020"
10 - "20" //> -10
10 + [1, 2] //> "101,2"
10 - [1, 2] //> NaN
По неочевидному поведению this, и ещё по нескольким причинам, как правило, приводящим к выстрелам в ногу на пустом месте, пока не выучишь конкретное поведение конкретной фигнюшки во всех возможных комбинациях, зато потом будешь ими хакать и взрывать мозги окружающим.
А по плавающей запятой претензий что-то не видно. Если кто-то называет язык глупым по подобной причине, его можно смело, вообще без каких либо вопросов, гнать учить машинные представления чисел, до просветления. Или(!) даже заставить его выучить хотя бы один, любой, популярный язык: подобные претензии явно указывают, что оратор вообще не понимает о чём говорит и принципиально ничем не владеет. Вот ещё, на громких неучей срываться, и писать для них статьи, метать перед ними бисер. Они же даже не попытаются научиться, да и у них глобально другая цель: обозвать что-то «глупым», возвышая себя над целым комьюнити «тупиц, которые пишут на своём глупом языке».
Мне всё-таки кажется, что фраза "какой глупый язык" не более чем просто ирония. Не думаю, что автор действительно считает обозначенные языки глупыми.
10 + "20" //> "1020"
10 - "20" //> -10
10 + [1, 2] //> "101,2"
10 - [1, 2] //> NaN
Складывать разные типы данных не изучив принцип их конвертации это конечно сильно, но увы, зачем изучать язык — давайте сразу писать, а потом если что —
Подобные нюансы обычно стреляют в ногу не потому, что кто-то специально решил складывать разные типы, а потому что где-то в длиной цепочке вызовов возникает ошибка. Ну например внешний сервис вернул вместо числа булевое значение или массив. Да, конечно же тип ответа нужно на каждом шагу проверять, но ошибки случаются.
Это только одно из мнений, их множество.
В Python'е, например, строгая типизация, никаких лишних преобразований типов, и в результате он проще для изучения.
В старых Бейсиках тоже были функции STR$, VAL и др., и никаких автоматических конвертаций.
А в ЖабоСкрипте — сначала создаем проблемы на ровном месте, а потом их героически преодолеваем. Язык и так непрост для новичков (лямбда-выражения, особое прототипное ООП, правила использования this), так еще зачем-то дополнительно решили усложнить.
В Python'е, например, строгая типизация, никаких лишних преобразований типов, и в результате он проще для изучения.
И такие же два оператора сравнения:
==
и is
.В старых Бейсиках тоже были функции STR$, VAL и др., и никаких автоматических конвертаций.
Сейчас перепроверил: в VB4 (1995) уже были автоматические конвертации, и
"1"+2
равнялось 3. Они сохранились в VBA и по сей день.Нет, is
и ==
это аналог ==
и .equals()
из java, а не ===
и ==
из js.
В питоне и яве есть сравнение ссылок, а есть семантическое сравнение описываемое для каждого типа отдельно. А в js есть сравнение с приведением типов, а есть с проверкой равности типа.
А в js есть сравнение с приведением типов
Для чего? Хоть одна задача, где требуется именно == вместо стандартного ===? Совершенно ненужная, лишняя функциональность. Как и собственно приведение типов в целом. Интересно, откуда вообще взялась такая маразматическая идея?
Не, то есть я могу сейчас загнать на пол экрана про историческую перспективу js которая привела нас туда где мы есть. Или про полтора случая когда ==
правда удобнее ===
и три с половиной когда автоприведение типов по версии js это удобно.
Но я так и не смог понять, что вас заставило задать этот вопрос именно мне :)
Ну, история известна. Создатель JS хотел разработать диалект Схемы, но в Mozilla решили, что это будет слишком сложно для пользователей. Пришлось в спешке переделывать, и получился непонятный мутант с синтаксисом Java и горой костылей. Но вот зачем было вводить в язык 2 типа сравнений, совершенно непонятно. За 8 лет, когда приходилось заниматься фронтом, я всегда использовал исключительно ===, и все знакомые поступают так же. Даже гуру Илья Кантор не смог придумать задач, где нужно нестрогое сравнение.
Так никто и не вводил два))
Сначала ввели один, потом сказали ойблин, но менять было поздно, так что ввели просто второй который работает как надо :)
А удобен ==
не как оператор нормального сравнения, а как такое странное сокращение когда в проекте все в курсе. То есть x == null
читается как isNullOrUndefined(x)
. Это не прямо хорошо, но это распространённая идеома которая не вредит когда все привыкли. Слава богу x==x
как аналог Number.isNan(x)
не многим короче и не получил такого распостранения. Что ещё... Ну все знают +x
. Оно настолько распространено, что я сходу и не назову "правильный" способ парсить строку в число. Number(x)
кажется?
Ну ещё если знаешь, что у тебя в строках честно-честно только цифры, то пока никто не видит можно написать что-то вроде:
let sum = 0;
numberStrings.forEach(s=>sum+=s);
Если [] == "" соответствует спеке Хаскеля, то меня ничего не смущает.
В реальности этот принцип не работает, потому что у всех свои собственные ожидания от языка. Например, я ожидаю, что пустая строка кастанется к false, но Ruby считает по-другому.
Поэтому мораль простая: ты или знаешь спеку или не знаешь.
Для описания поведения сравнения в JS нужно помнить больше вещей, как я понимаю.
Достаточно запомнить правило «никогда не используй ==». А если забудешь, то статический анализ напомнит.
Принцип наименьшего удивления – это маркетинговый ход Ruby, который привел к тому, что у них есть три метода для взятия длины строки (length, size и count). Чтобы уж точно никого не удивить.
Count совсем не для этого. Ну а length и size, по сути, псевдонимы. Они по факту вызывают одну и ту же функцию.
Некоторые ребята называют JS глупым языком из-за странных автоматических приведений типов, вроде
Ребята, у которых в реальном коде складываются числа с массивами, я извиняюсь, — говнокодеры. Если код нормально писать, никогда в эти тонкости приведения не придеться вникать.
С помощью одного значения BigInt мы можем представлять произвольно большие целые числа. Но если мы создадим пару из целых чисел, то сможем представлять произвольно большие или маленькие числа.
Можем, но практического смысла в этом нет, т.к. арифметические операции с рациональными дробями могут очень быстро раздувать числитель и знаменатель до достижения переполнения. Например, если в цикле складывать числа с разными знаменятелями, метод simplify перестанет справляться со своей задачей. Решая эту проблему, мы введем погрешность, заменив simplify на нормализацию и… изобретем float
Ну, он очень простой по устройству. Гениально простой, ненамного сложнее Lisp.
А для использования… Ну, я не думаю, что он проектировался для использования в тех масштабах, которые мы имеем сейчас – только и всего. Если бы по прежнему речь шла о 10 строчках для создания необычного функционала на страничке – никто б не жаловался, это можно и на brainfuck написать.
И чем же он простой?Видимо имелся в виду вариант из середины 90-х, когда JS действительно был простым и понятным.
И тут Javascript не лучше и не хужеВот здесь интересный момент: есть три библиотеки, написанных на C, Python и JS. У них есть внешние зависимости. На С их 3, на Python 9, на JS подтягивается аж 705 внешних пакетов. Понятно, что это типично для JS экосистемы, но все же как-то это смущает, что может понадобиться искать ошибку в таком количестве модулей.
Видимо имелся в виду вариант из середины 90-х, когда JS действительно был простым и понятным.
Отчасти да. Только понятным он не был. Он был прост внутри – но примерно все понимали его неправильно. В силу сходства с Java ожидали и схожего поведения, для 10 строк кода это обычно срабатывало, а потом уже начинало ломаться.
В общем, жаль, что Айку не дали просто воткнуть в Netscape Scheme, дали задание на Java-подобный язык.
на JS подтягивается аж 705 внешних пакетов.
Ну, это психологический выверт, привнесённый, как я понимаю, node.js, а не самим языком. См. историю с left-pad.
Я сказал по устройству, а не по использованию :-).
По устройству прототипная модель проще, чем классы с VMT, this в JS сделан максимально примитивно (this задаётся при вызове. Для сравнения посмотрите реализации делегатов на C++. До стандартизации std::function было довольно уныло).
Промисы и деструктурирующее присваивание появились позже, но даже они устроены довольно просто. Это не значит, что при их использовании не выстрелишь себе в ногу (наоборот) – это значит, что сделать их реализацию на коленке можно за разумное время.
JS был моим первым языком, ничего плохого не случилось. Прототипы, промисы, this, деструктурирование, event loop и так далее хорошо понимаю, а в те времена, когда я его изучал, это и вовсе был es4, верстка под ie6 и так далее.
https://habr.com/ru/company/mailru/blog/335292/#tochnost-vychisleniya-01--02
Вот, например, на хабре. Вообще в гугле много результатов по этому запросу. В большинстве из них есть пометка, что претензия абсурдная.
Вы говорили про железо, но это и к программированию относится, сейчас разработчики обычно не знают как работают инструменты под капотом.
Если им показать как сделать SPA без фреймворков и библиотек, у них еще и волосы дыбом встанут, и собес не пройдешь J
По моему опыту, что я вижу, сейчас почти весь рынок забит "React-разработчиками", которые дальше библиотеки и компонентов плюс гайдов как работать с сагой и прочим, не работают.
Поэтому я сейчас рынок специалистов воспринимаю как рынок каменщиков, где люди умеют ложить один конкретный кирпич, и всё. Дальше дай им сложную задачу, и всё рассыпется от отсутствия знаний и опыта, потому что дальше Реакта они не смотрели.
Честно говоря, завис на выражении
const one = new Ratio(BigInt(1), BigInt(0));
Я до этого думал, что второй параметр — это знаменатель, но ...
Умножьте число на 100 или 1000, и будет вам счастье.
0.1 + 0.2 = 0.300...4
или если умножить на 100
(0.1 x 100) + (0.2 x 100) = 30
30 / 100 = 0.3
Или этот вариант может давать сбой?
Только 30 на 100 делить не надо :-)
И да, после исправления ошибки получается true; но это случайность.
Кстати, интересный эффект. Если сравнивать с 0.3 то true, но с делением (30 / 100) выходит false, при том что 30 на 100 делится без погрешности (0.3).
Есть идеи почему работает именно так?
Насколько я понимаю, здесь нет проблемы с дробями, т.е. должно работать.
Число 0.1 НЕ представимо точно в двоичном виде. Поэтому компьютер использует его приближение, равное «0.100000001490116119384765625» (для 32-битного float). То же самое и с числом 0.2 — оно будет равно «0.20000000298023223876953125». А сложение компьютер всегда делает с абсолютной точностью, вот вы и получаете «неточный» результат. Просто надо помнить про то, что далеко не все числа представимы точно и НИКОГДА не использовать прямое сравнение в случае чисел с плавающей точкой. И все будет хорошо.
p.s. даже пи представимо примерно в компьютере, для 32-х бит это будет 3.1415927410125732421875, для 64-х бит это будет 3.141592653589793115997963468544185161590576171875 (я выделил точные знаки), как видите при любой точности для пи у вас будет только примерно треть точных знаков.
Про сложение с плавающей точкой сильно круто сказано. Степень основания может сильно отличаться и мантиссу меньшего числа можно будет усечь или даже проигнорировать без потери точности результата. Это числа с фиксированной точкой при сложении как целые, и тоже могут переполняться.
Если в языке нет точных чисел никакие масштабные коэффициенты или предложение хранить деньги в виде целого числа копеек не помогут.
В том же PL/1 точка является признаком только дробной части, а вот показатель степени «E» — уже признаком приближенного числа.
Поэтому в PL/1 0.1Е0+0.2Е0 не равно точно 0.3Е0 как и в других языках (как и в примере).
А вот 0.1+0.2 точно равны 0.3 поскольку это точные числа, представленные в двоично-десятичном виде.
если бы график был маленьким, а значения данных были большими, я бы получил ошибки округления. И зачастую это нормально. Но на графике некоторые пиксели должны выстраиваться в линию. Иначе рисунок выглядит неправильно.
Проблема выглядит слегка надуманной. Не совсем понимаю, каким образом в свечном графике можно заметить несоответствие пикселей из-за округления.
А так, рациональные числа это забавно, но узок круг применения. Стоит попытаться применить их к каким-то кругам/тригонометрии/площадям — так всё и сломается.
Но JavaScript точно не заслуживает возмутительных шуток.
То есть вы нашли (а может сами придумали) одну-единственную действительно глупую шутку, и на основании этого сделали вывод что JavaScript не заслуживает возмутительных шуток? Нет, заслуживает, ещё как. Просто пример выбран неудачный.
А хотите удачный? Нет 64-битных целых. Да что там, вообще нет целых, но реально напрягает именно отсутствие 64-битных. То есть чтобы разобрать например json (JavaScript Object Notation, на секундочку) с 64-битными целыми, придётся сначала регулярками (или как-то ещё) превратить эти числа в строки, потом разобрать json, а потом уже как-то обрабатывать эти строки, вероятнее всего преобразовав в bigInt. Множество операций для решения проблемы, которая в здравых языках вообще не существует. И это — только один из примеров неадекватности языка.
Для вывода пользователю каждый новичок возьмет на вооружение простое округление
Math.round((0.1 + 0.2)*10)/10
или
+(0.1 + 0.2).toFixed(1)
const equals = (one, two) =>
one.numerator * two.denominator === two.numerator * one.denominator;
И так же со сравнениями.
Устали от глупых шуток о JS? Напишите свою библиотеку