Developer's notes
33 subscribers
67 photos
4 videos
74 links
Пишу обо всём и ни о чём, могу и о программировании
Download Telegram
Состояние потока.

Жара только усиливается – пообещали нам +34 сегодня. Такая погода явно требует некой жест^ технического контента. И вспомнилось мне одно собеседование и тестовое задание на нём. Как я уже говорил: собеседования – это отдельный жанр (кринжа), при этом если вы разработчик и хотите получать адекватную зарплату – зло абсолютно неизбежное. Если учесть, что многие компании имеют 2ух – 6ти ступенчатые собеседования, то проведя достаточно лет в индустрии, есть шанс пройти собеседований не меньше, чем было экзаменов в Вузе.

Впрочем, в эту довольно известную и крупную российскую компанию, менявшую имя несколько раз, собеседования не шокировали числом и сложностью, всё было довольно стандартно, кроме тестового задания: созвон с HR, тестовое задание, созвон с командой, созвон с тех директор чего-то там. В итоге не взяли на последнем этапе – скорее всего нашли разработчика подешевле, никаких сильно отрицательных эмоций это не вызвало у меня, впрочем, положительных тоже. Поскольку собеседования, как известно, не покрыты NDA, ничто не сдерживает меня от опубликования тестового задания и его решения.

Тут хочу отметить, что я, не советую вам соглашаться на практику тестовых заданий, потому что: а) как правило это не оплачивается б) у нас сейчас рынок кандидата. Иначе говоря, соглашайтесь на это только если вы начинающий разработчик либо вам просто интересно посмотреть, что они там хотят. Данное тестовое задание было действительно не слишком сложным и длинным, без дальнейших променадов перейду к нему.

Нужно написать консольное приложение на C++, в нём должно быть 2 потока, один из них выводит ‘1’ в стандартный вывод, второй выводит ‘2’ в стандартный вывод, при это единицы и двойки всё время чередуются, переносов строк – нет, программа работает бесконечно. То есть вывод должен быть “12121212….”. Использовать потоки и средства синхронизации из стандартной библиотеки (std).

Ниже приведу версию без всякой синхронизации, которая, конечно, работает неправильно, а вот правильные ответы и разбор – будут в следующих постах.

#include <iostream>
#include <thread>

using namespace std;

int main()
{

auto firstRoutine = [](){
while (true){
cout << '1';
}
};

auto secondRoutine = [](){

while (true){
cout << '2';
}
};

thread th1{firstRoutine};
thread th2{secondRoutine};


th1.join();
th2.join();

return 0;
}

#job #C++ #Около_ИТшное #interview #собесед #today #memories #c_plus_plus #IT
🔥2
Сложите это, пожалуйста

Написав эту заметку про типы с плавающей точкой, вспомнил, что был и другой интересный случай с этими числами, правда, из практики собеседований, но не суть. Вопрос звучал примерно так: дан массив чисел типа double, как лучше его отсортировать (по возрастающей или убывающей), чтобы просуммировать его с большой точностью.

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

Начну издалека: когда нас в детстве учат устному счёту 1, 2, 3…нас учат натуральным числам N и их свойствам - числам, которыми я могу посчитать количество предметов, далее, “добавляя” к ним 0 и отрицательные числа, мы получаем Z - целые числа, “присоединяя” к ним обыкновенные дроби мы получаем множество Q – множество рациональных чисел , наконец, решая квадратные и не только уравнения и рассматривая другие конструкции, мы получаем множество вещественных чисел R (примеры, корень из двух, экспонента и так далее). Эту цепочку можно было бы продолжить: комплексные числа, дуальные, кватернионы, а в вещественных выделить алгебраические и трансцендентные…Но я не буду об этом говорить, потому что, они все не имеют отношения к тем числам, что мы имеем в C++ как базовый типы. Об этом немножечко позже.

Непосредственно с числами, даже каким-то образом со способом их определения, связанны основные арифметические операции: сложение, вычитание, умножение, деление. На эту заметку нам хватит сложения: даже эти операции не так уж элементарны…особенно деление. Бинарная операция сложения (т.е. принимающая два аргумента) для множеств N, Z, Q, R (на самом деле и все остальные как я помню, но это не важно) обладает свойствами коммутативности и ассоциативности. Коммутативность — значит всего лишь, что a+b = b+a, совсем просто, а вот ассоциативность, посложнее: a + (b+c) = (a+b) + c. Иными словами, операция у нас бинарная, мы умеет сложить 2 числа, а вот если написать, что мы складываем 3 числа, то можно сначала сложить первые 2, или последние 2, но результат будет одинаков. Если вы думаете, что это какая-то глупость и так всегда, то посмотрите на вычитание: (1 – 2) – 3 = -4, но 1 – (2 – 3) = 0 (вычитание не ассоциативно). На самом деле Z – это кольцо, Q, R – это поле, но мы с этим не будем работать.

В C++ (и во многих остальных ЯП), есть различные операторы, конечно, включая и арифметические, не буду тут давать формального определения оператора в С++, понятно, что это некоторая синтаксическая сущность, тут важно, что для всех операторов стандарт языка предоставляет информацию об их ассоциативности, а именно, либо слева направо, либо справа налево. Большинство слева направо, но нас интересует только operator +.

Прошу ещё раз заметить: в C++ a+b+c это (a+b) + c, то есть в математике, скобки я могу ставить, как угодно, а компилятор, видя выражение без скобок, неявно поставит их таким образом, то есть, на самом деле, нет там никаких скобок, а есть ассемблерные инструкции (картинки с https://godbolt.org/ будут под постами)

#IT #job #c_plus_plus #math #weird #interview
Продолжение

Теперь вернемся, к тому какие встроенные типы для чисел в C++ мы имеем – integra и floating point…Первые мы пропускаем, насчёт вторых зададимся вопросом, вот в верху я представил множество различных чисел из алгебры – к чему относятся floating points? Правильный ответ: float и double способны представить некоторые рациональные числа. На этом всё, да, никаких настоящих вещественных чисел они не могут представить, корень из двух будет аппроксимирован некоторым рациональным приближением и так далее. Но, выше я писал, что рациональные числа обладают коммутативностью и ассоциативностью по сложению? Да…но… Уже случай из предыдущего поста, явно не ложится в наше пониманием рациональных чисел, демонстрируя, некоторое “поглощение”. Сложение чисел с плавающей точкой имеет свойства отличные от общеалгебраических, в результат их представления и реализации арифметических операций над ними. При этом, CPU, как и целочисленными значениями, складывает по 2 числа с плавающей точкой за одну инструкцию, было бы странно, если б мы сломали коммутативность: это был бы очевидный баг.

Я утверждаю следующее: можно найти тройку чисел с плавающей точкой (одновременно все float или double), такие что (a+b) +c не равно a + (b +c).
И сейчас я объясню, как я собираюсь их искать. Опять апеллирую к примеру с поглощением малого числа из прошлого поста, идея сводится к тому, что для начала мне нужно найти пару чисел, “большое” число a, и “малое” число b. Причём, число a “поглощает” число b, но число 2*a уже не будет поглощаться… Ещё раз: поскольку это поглощение основано на том, что при сложении числа выравниваются (представляются с одинаковыми экспонентами), малое число должно быть настолько малым, что б выровняться за правый край мантиссы. А если я умножу его на 2 (сдвину на разряд влево) оно учтётся пройдёт по последнему разряду мантиссы и учтётся.

Так же, я хочу числа с простым бинарным представлением, число a=1, число b…подсмотрим сюда, мантисса у нас 23 бита, т .е. я предполагаю, что нужно сдвинуть единицу направо на 24 разряда, иначе говоря мантисса у нас опять 1, а экспонента -24 (минус 24). Да нам удобнее для числа b использовать так называемую научную нотацию, да ещё и наполовину в 16ричной системе счисления…
Вот листинг, берем float т.к. он короче, и берем format т. к. он обещает “умный вывод”, с нулями только если они имеют смысл, и собираем всё с C++20:

#include <iostream>
#include <format>

int main()
{
float a = 1.0;
float b = 0x1.p-24;

std::cout << std::format("{}", a+b) << std::endl;
std::cout << std::format("{}", (a + b) + b) << std::endl;
std::cout << std::format("{}", a + (b + b)) << std::endl;

return 0;
}


Вывод будет:
1
1
1.0000001

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

#IT #job #c_plus_plus #math #weird #interview