vak: (Знайка)
[personal profile] vak
На собеседованиях я часто спрашиваю программистов, как сложить на Си два целых числа. Проверим, как с этим справится Гугл Бард.
Please create a function "int add(int a, int b)", which adds two integer numbers and avoids undefined behavior.
Мгновенный ответ:
Sure, here is the C function int add(int a, int b) that adds two integer numbers and avoids undefined behavior:
int add(int a, int b) {
  int sum;

  /* Check for overflow */
  if (a > 0 && b > 0 && (a > INT_MAX - b)) {
    /* Overflow detected */
    return INT_MAX;
  } else if (a < 0 && b < 0 && (a < INT_MIN - b)) {
    /* Overflow detected */
    return INT_MIN;
  }

  /* Add the two numbers */
  sum = a + b;

  return sum;
}
This function first checks for overflow by comparing the two numbers to the maximum and minimum values for an integer. If either number is greater than the maximum value or less than the minimum value, then the function returns INT_MAX or INT_MIN, respectively. Otherwise, the two numbers are added together and the result is returned.

This function avoids undefined behavior by checking for overflow before adding the two numbers. If overflow is detected, the function returns a safe value instead of crashing or producing unexpected results.
За последние несколько лет ни от кого из сапиенсов я не получал настолько быстрый и исчерпывающий ответ. Но продолжим:
Please create a few tests for this function.
Пауза чуть подольше:
Sure, here are a few tests for the add() function:
#include <stdio.h>

int add(int a, int b);

int main() {
  int expected, actual;

  /* Positive numbers */
  expected = 10;
  actual = add(5, 5);
  if (expected != actual) {
    printf("add(5, 5) expected %d, got %d\n", expected, actual);
    return 1;
  }

  /* Negative numbers */
  expected = -10;
  actual = add(-5, -5);
  if (expected != actual) {
    printf("add(-5, -5) expected %d, got %d\n", expected, actual);
    return 1;
  }

  /* Overflow */
  expected = INT_MAX;
  actual = add(INT_MAX - 1, 1);
  if (expected != actual) {
    printf("add(INT_MAX - 1, 1) expected %d, got %d\n", expected, actual);
    return 1;
  }

  /* Underflow */
  expected = INT_MIN;
  actual = add(INT_MIN + 1, -1);
  if (expected != actual) {
    printf("add(INT_MIN + 1, -1) expected %d, got %d\n", expected, actual);
    return 1;
  }

  return 0;
}
These tests cover a variety of cases, including positive numbers, negative numbers, overflow, and underflow. If all of these tests pass, then it is likely that the add() function is working correctly.
Неплохо, но я же дотошный, продолжаю усугублять.
Please use Googletest for tests.
Этого Барда ничем не проймёшь.
Sure, here are the same tests written using GoogleTest:
#include <gtest/gtest.h>

int add(int a, int b);

TEST(AddTest, Positive) {
  EXPECT_EQ(10, add(5, 5));
}

TEST(AddTest, Negative) {
  EXPECT_EQ(-10, add(-5, -5));
}

TEST(AddTest, Overflow) {
  EXPECT_EQ(INT_MAX, add(INT_MAX - 1, 1));
}

TEST(AddTest, Underflow) {
  EXPECT_EQ(INT_MIN, add(INT_MIN + 1, -1));
}
These tests are written using the GoogleTest framework, which provides a more structured and organized way to write tests. The EXPECT_EQ() macro asserts that the two arguments are equal. If they are not equal, the test will fail.

These tests can be run using the gtest command-line tool. For example, to run all of the tests, you would use the following command:
gtest add_test.cc
If all of the tests pass, then the output will be:
[==========] Running 4 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 4 tests from AddTest
[ RUN      ] AddTest.Positive
[       OK ] AddTest.Positive (0 ms)
[ RUN      ] AddTest.Negative
[       OK ] AddTest.Negative (0 ms)
[ RUN      ] AddTest.Overflow
[       OK ] AddTest.Overflow (0 ms)
[ RUN      ] AddTest.Underflow
[       OK ] AddTest.Underflow (0 ms)
[----------] 4 tests from AddTest (0 ms total)

[----------] No test cases ran.
[----------] No assertions have been violated.

[==========] 4 tests passed.

Ну что сказать. Искусственный интеллект становится реальным помощником программисту.

Date: 2023-04-24 23:24 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
Попроси убрать лишние подвыражения в проверках на переполнения.

Date: 2023-04-25 00:22 (UTC)
ircicq: (Default)
From: [personal profile] ircicq
Это ещё вопрос кто кому помощник.

Были ли лифты помощниками лифтёров?

Date: 2023-04-25 00:32 (UTC)
spamsink: (Default)
From: [personal profile] spamsink
Тесты переполнения на самом деле не тестируют на переполнение.

Date: 2023-04-25 04:16 (UTC)
archaicos: Шарж (Default)
From: [personal profile] archaicos
Строго говоря, даже если бы тестировали бы, то тестировали бы предотвращение переполнения.

Date: 2023-04-25 06:19 (UTC)
x86128: (Default)
From: [personal profile] x86128
С другой стороны, сетку просили "avoids undefined behavior" оно и выдало с творческим подходом, правда дальше зачем-то нафантазировало про переполнения, но и так сойдет. К тому что цель достигнута, пусть и результат не совсем корректный, но поведение функции теперь "defined behavior" %)

Date: 2023-04-25 01:51 (UTC)
From: [personal profile] ichthuss
Надо будет на досуге задать ему любимую задачку [personal profile] juan_gandhi: написать функцию f: int32 -> int32 (значения выражаются в дополнительном коде), для которой выполняется f(f(x)) = -x.

Date: 2023-04-25 06:25 (UTC)
From: [personal profile] chabapok
Чтобы двойная прогонка давала отрицательное? Это задача больше математическая, а не на умение выразить мысль в коде.

Date: 2023-04-25 13:27 (UTC)
From: [personal profile] ichthuss
Математическая, но программист без математики - максимум кодер. Тем более задача на один из наиболее распространенных в жизни типов данных.

Date: 2023-04-30 10:02 (UTC)
From: [personal profile] chabapok
у меня тут было время, я эту задачу крутил... А она именно так звучит? Способов я нашел дохрена, но у меня всегда пара чисел "лишних" остается, для которых не получается выполнить требуемое условие.

такое можно сделать по модулю некоторых простых чисел. Например по модулю 73 имеем: 27*27=72. А 72 по модулю 73 ведет себя как -1. Поэтому, если у нас арифметика по модулю 73, то такое делается умножением на 27.

Поэтому везде, где по модулю "p" выражение (p-1) имеет корень квадратный, такое делается просто умножением на соответствующее число. Как считать символы лежандра и корни по модулю - относительно легко найти в гугле.

Но в арифметике по модулю 2^n такой финт уже не прокатывает.

Задачу не считаю задачей по программированию, хотя в ней и используется двоичный код.

Date: 2023-04-30 21:32 (UTC)
From: [personal profile] ichthuss
В этом-то и суть. Такую функцию построить теоретически невозможно. А для программиста важной компетенцией является определять невозможность того, чего от него требуют - иначе окажется крайним.

Date: 2023-05-01 10:51 (UTC)
From: [personal profile] chabapok
"найти причину по которой можно отлынивать от работы" - как вопрос на собеседовании. Офигенно %)

Date: 2023-05-01 13:29 (UTC)
From: [personal profile] ichthuss
А зачем фирме работник, который возьмется за заведомо невыполнимое и предсказуемо запорет?

Date: 2023-05-02 07:32 (UTC)
From: [personal profile] chabapok
Концепция хорошая, другое дело что в нашем несовершенном мире такое редко случается. Обычно если задача изначально выглядит решаемой, то нужно сначала приложить определенные усилия, чтобы понять, что это не так. После этого надо задуматься "а что делать?", возможно обсудить с начальством/коллегами, и обычно что-то делать все же нужно, иначе заказчик же не заплатит.

Так что довольно часто частичное решение лучше полного нерешения. Возьмем, например, деление. Если ты начнешь отлынивать от работы по причине "понимаете, в знаменателе может же быть 0, так что задача деления чисел нерешаема" - то тебя не поймут

Но принципиально же f:int32->int32 не отличается от f(a,b) = a/b . Так что, написать программу, которая бросает исключение при недопустимых входных данных - это вроде бы ок.

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

- - -

Вспоминается mevamevo. mevamevo проводит у себя в журнале блиц-опросы в стиле что-где-когда, и если ответ формально правильный но не тот, что им персонально ожидалось, то mevamevo говорит что-то вроде "ответ правильный но не тот что я ожидал, поэтому ответ на засчитываю". Причем, совесть его во время этого вообще не грызет, он это говорит как будто так и должно быть. Хотя опять же, если берешь на работу человека, который видно, что мыслит правильно но совсем по другому, то с ним можно и несработаться. Поэтому, если задача в формате приема на работу - то это одно, а если спрашивается в формате чгк - то дать отворот это значит быть охуевшим как mevamevo.

Date: 2023-05-02 19:42 (UTC)
From: [personal profile] ichthuss
Ну, собственно, если бы мне задали такую задачку, я бы сказал: в строгом виде она нерешаема. Если хотите, могу привести доказательство нерешаемости, а если хотите, могу написать реализацию, работающаю для всего множества за исключением двух заданных чисел x и -x.

Кстати, совершенно аналогичная, часто встречающаяся и имеющая значительные последствия ошибка: попытка парсить XML регулярными выражениями.
Edited Date: 2023-05-02 19:44 (UTC)

Date: 2023-05-03 05:37 (UTC)
From: [personal profile] chabapok
Задачи в стиле "парсить XML регулярными выражениями" имеют больше не практическую, а учебную пользу. Если возвести в степень абсурда: погуглите "написала тетрис на sed".

Date: 2023-05-03 10:24 (UTC)
From: [personal profile] ichthuss
Я sed'ом пользовался немного, но говорят, что он тьюринг-полный, то есть любая в принципе решаемая задача решаема на sed'е. Регулярные выражения, напротив, годятся только для разбора регулярного языка. Язык, не являющийся регулярным - тот же XML - регексами корректно распарсить нельзя в принципе. Иными словами, какое регулярное выражение ни возьми, обязательно существует такой корректный XML, который этот регекс не распарсит.

Date: 2023-04-25 06:12 (UTC)
dimorlus: (Default)
From: [personal profile] dimorlus
Я уже тестировал ChatGPT этим вопросом

Calculate weight of copper wire 0.2548mm diameter and 13.5ohm resistance

И ни разу не получал правильного ответа (последнее время ChatGPT чаще висит, чем работает, народ как-то тестирует ChatGPT 4, но я не пробовал пока)

попробовал и с Bard, он расписывает шаги, и выдает

m = (1.72 × 10⁻⁸ Ωm)(π(0.001274m)²)(1m) = 3.52 × 10⁻⁶ kg
or approximately 0.00352 grams.

Правильный ответ (с самодельным калькулятором, понимающим gauge проводов, и учетом 1% удлинения провода при намотке))

(8.933/0.0172)*((cs(#30)-1%)^2)*13.5=17.83g

Что ж эти системы арифметики не знают?

PS ChatGPT просрался и утверждает Therefore, the weight of the copper wire is approximately 18.71 kilograms., то есть уже почти правильно, только в 1000 раз больше.
Edited Date: 2023-04-25 06:19 (UTC)

Date: 2023-04-25 06:42 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
Складываем два интеджера и возвращаем макс инт если переполнилось? Не, ну как пример кода ок, но как это можно практически использовать то?

Date: 2023-04-25 07:41 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
Ну я вот про ерунду и задумался, потому что какой смысл складывать если оно вылетит в переполнение? Значит дизайн плохой в первую очередь!

Date: 2023-04-25 08:53 (UTC)
From: [personal profile] ex0_planet
А какой здесь может быть хороший? Wrap around даёт на выходе мусорные данные неотличимые от валидных, разрядная сетка пошире не всегда (чаще всего) недоступна, разные NAN и исключения тоже неприменимы из-за особенностей вычислительного конвейера. Еще варианты?

Date: 2023-04-25 09:09 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
и вот как ответственный девелопер это я и должен озвучить архитекту. пусть думает что в его дизайне не так, ему за это деньги платят )))

Date: 2023-04-25 09:13 (UTC)
From: [personal profile] ex0_planet
В problem domain где подобные техники актуальны, вот это ваше "озвучить архитекту" эквивалентны одиночной итальянской забастовке. С соответствующими оргвыводами.

Date: 2023-04-25 09:32 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
возможно. в моем приземленном домейне и проблем таких не возникает обычно.

Date: 2023-04-25 09:26 (UTC)
tiresome_cat: (CuriousCat)
From: [personal profile] tiresome_cat
Ну почему сразу плохой дизайн? Верифицировать (нормализовать) входящие данньіе всегда полезно для здоровья, а если их генерирует человек - даже необходимо. Против дурака архитектура бессильна. Классика же: "мусор на вход е - мусор на вьіходе".
Edited Date: 2023-04-25 09:30 (UTC)

Date: 2023-04-25 09:33 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
я млохо могу себе представить реалистичную задачу под это решение в любом случае, мусор там на входе или нет.

Date: 2023-04-25 09:47 (UTC)
tiresome_cat: (CuriousCat)
From: [personal profile] tiresome_cat
Чой-то? Вот, например, суммирование двух PCM-coded зуковьіх потоков. Там ограничение максимального значения как раз вьіглядит вполне естественно. Наличие в вьіходном потоке значений +/- INT_MAX при єтом может бьіть основанием для аппаратной регулировки уровня входного сигнала. Но уже в другой функции :)
Edited Date: 2023-04-25 09:54 (UTC)

Date: 2023-04-25 11:50 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
далеко от моей предметной области :)

Date: 2023-04-25 10:38 (UTC)
pritkiy_kaban: (Default)
From: [personal profile] pritkiy_kaban
Показывать студентам как пример: вот что бывает, когда сделано корректно, а прилететь может как с датчиком температуры на TMI :)

Date: 2023-04-25 11:50 (UTC)
ordinary_joe_backup: (Default)
From: [personal profile] ordinary_joe_backup
вот вот

Date: 2023-04-25 08:13 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Это же тестовые задания на программистские интервью. Даже Гугл в конце концов догадался, что это совершенно негодный критерий для выбора.

Date: 2023-04-25 18:25 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Как работает у человека голова, -- это лучше выяснять в простом разговоре.

Тем более, что вопрос "сможет ли написать сортировку списка?" снят наличием сервисов в интернете и к реальности уже не относится.

Date: 2023-04-25 18:46 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Сильно подозреваю, что кое-кто за рассуждения принимает обычный квазиинтелигентный бред. Гнать пургу учат ещё в школе.

Date: 2023-04-25 08:32 (UTC)
juan_gandhi: (Default)
From: [personal profile] juan_gandhi

Интересно; хороший пример.

Date: 2023-04-25 19:08 (UTC)
fizzik: (Default)
From: [personal profile] fizzik
А не надо складывать два целых числа на Си!
Надо складывать их на C++ и в случае переполнения кидать исключение.

Date: 2023-04-25 21:47 (UTC)
From: [personal profile] dijifi
Acceptable undefined behavior may be “the processor turns into a lizard and runs away”

Date: 2023-04-25 22:12 (UTC)
fizzik: (Default)
From: [personal profile] fizzik
Я имею в виду, что вместо того, чтобы вовзращать из функции сложения выделенное значение, сигнализирующее о переполнении, можно бросить исключение, сигнализирующее о том же. Очевидное преимущество в том, что в длинной цепчке вычислений нам не надо тогда проверять после каждого шага, не случилось ли переполнения. Ведь если у нас 50 шагов вычислений, то в большинстве случаев не важно, получили ли мы переполнение уже на первом шаге, или на 50-м. Важно только, что корректного результата у нас нет. Код, который после каждого из 50 шагов будет проверять результат на выделенные значения чтобы свалиться в ветку обработки ошибки, окажется абсолютно нечитаем. Вместо этого мы можем писать наши вычисления как бы не задумываясь о переполнениях (однако используя не стандартные арифметические операции, а функции вроде написанной ботом, но бросающие исключения), а ветку обработки переполнения выполнять, поймав исключение там, где это удобно.

Date: 2023-04-28 11:56 (UTC)
waqur: (Default)
From: [personal profile] waqur
Ну, тут с искусственным болванчиком можно поспорить.

Это решение хорошо для языковых пуристов и архитектурных астронавтов, бесконечно спорящих о гипотетических процессорах, где отрицательные целые числа не представлены в дополнительном коде или где по умолчанию применяется суммирование с насыщением.

На практике обычно бывает по-другому: надо заставить чёртов компилятор вставить самую обычную инструкцию ADD (которая работает максимально быстро и с учётом представления в дополнительном коде реализуется в железе одинаково для сложений со знаком и для сложений без знака), за которой просто проверить вычисленный в железе флаг переполнения (который называется OF в случае x86 и amd64, когда идёт речь о сложении со знаком; для засекания переполнения при беззнаковых сложениях есть другой флаг, CF).

И это делается через __builtin_add_overflow: https://godbolt.org/z/j6MEMKcr6

А если начать выдрючиваться с проверкой знаков операндов, вычитанием из INT_MIN и прочими подобными выкрутасами, то даже если не ошибёшься по типу off-by-one error во всех этих условиях, то компилятор налепит кучу ветвлений, которые, как говорится, ни к селу, ни к городу: https://godbolt.org/z/737c6fxea

Если мы пишем на абстрактном Си для абстрактного процессора и самое главное не расстроить Его Величество Стандарт, то это решение как бы окэй (хотя с первого взгляда отсутствие off-by-one errors во всех этих условиях неочевидно, что впрочем компенсируется тестами); а если к списку требований добавляется ещё и приемлемая производительность готового решения, то задачка становится уже совсем другой: как заставить чёртов компилятор вставить инструкцию для самого обычного сложения, а затем достать из процессора те флаги, которые он уже и так "на халяву" для нас вычислил в результате этого самого обычного сложения.