Комментарии 141
НЛО прилетело и опубликовало эту надпись здесь
Все же не совсем понятно, чем плох вариант с #define.
Замусоривается глобальное пространство имен — может быть, но аналогичным образом оно замусоривается и с const, везде, где включается этот хедер.
Замусоривается глобальное пространство имен — может быть, но аналогичным образом оно замусоривается и с const, везде, где включается этот хедер.
Никто не мешает сделать как локальный дефайн (Уникальный причем!) так и локальную static const переменную.
Зачем мусорить?
Зачем мусорить?
Может, речь о таких ситуациях?
#define Pi 3.14
<...>
void foo()
{
int Pi = 0;//oops
}
Обычно у дефайнов и переменных разный стиль именования. Например, дефайн капсом с подчеркиваниями, а переменные строчными буквами. Поэтому они пересекаться не должны.
Кроме того, ну выдаст компилер ошибку, переименовать переменную не так трудно ведь?
Это же не сегфолт в рантайме, исправить легко.
Кроме того, ну выдаст компилер ошибку, переименовать переменную не так трудно ведь?
Это же не сегфолт в рантайме, исправить легко.
ну выдаст компилер ошибку, переименовать переменную не так трудно ведь?
При всем уважении, но «должны» это не про реальные проекты. Бывает всякое, я как-то боролся с проблемой, потому что некто определил макрос L. Было несколько переменных внутри структур с таким же названием, кроме того, — это лексема для указания «широких» строк. Размер проекта примерно 700мб исходного кода. Причем раньше это работало (много лет), но стало разваливаться при обновлении версии компилятора. По-другому включилась последовательность h-ников и все, приехали. Найти это при таких объемах было чрезвычайно сложно, во-первых потому, что имя короткое и оно плохо ищется. Во-вторых ошибка вовсе не указывала на проблемное место, а всплывала за много «километров» от него, с совершенно невразумительной диагностикой. Если бы у меня на тот момент уже не выработался паттерн: «видишь непонятную хрень — виноваты макросы», я бы потратил еще больше времени.
Так что, при всем уважении, ваши рассуждении просто говорят о том, что вы никогда не имели дел с legacy такого объема.
Это же не сегфолт в рантайме, исправить легко.
Сегфолты, к слову, обычно проще исправить, чем такое.
Да, с легаси такого объема я не сталкивался.
Возможно, в таких ситуациях все иначе. Я говорю про более мелкие проекты.
Возможно, в таких ситуациях все иначе. Я говорю про более мелкие проекты.
> потому что некто определил макрос L
Напомнили. Столкнулись с таким же, только макросом было W. Первые умники работают в Lotus (IBM), писали Lotus C API. А вторые умники добавили такое в boost. А нам, дуракам, которым надо было подключать и первое и второе, оставалось выбрать, кого патчить :-]
Напомнили. Столкнулись с таким же, только макросом было W. Первые умники работают в Lotus (IBM), писали Lotus C API. А вторые умники добавили такое в boost. А нам, дуракам, которым надо было подключать и первое и второе, оставалось выбрать, кого патчить :-]
имя короткое и оно плохо ищется
Вы должно быть забыли поставить запятую, т.к. из того, что имя короткое совсем не следует, что оно плохо ищется :) Например вот вам команда с регуляркой «навскидку», которую я бы на вашем месте использовал «grep -rnIE "\bL\b[^\"]*"» — скипает бинарные файлы и «широкие» строки, выводит только строки с их номерами в файле, где используется одинокая большая Эл.
Вы должно быть забыли поставить запятую, т.к. из того, что имя короткое совсем не следует, что оно плохо ищется :) Например вот вам команда с регуляркой «навскидку», которую я бы на вашем месте использовал «grep -rnIE "\bL\b[^\"]*"» — скипает бинарные файлы и «широкие» строки, выводит только строки с их номерами в файле, где используется одинокая большая Эл.
Допустим мы представим, что я не знаю про регулярки (только допустим). Допустим, что я не умею пользоваться grep (только допустим). Теперь допустим я решил попробовать поискать таким образом у себя и получил лог в полтора миллиона строк (и это я еще обрезал поиск только по h, c и cpp). «Плохо ищется» означало, я не получу обозримый лог поиска. Я получу портянку на пару недель изучения :) Во-вторых, если к твоей регулярке добавить еще поиск вхождения по «define», то становится гораздо проще. Но это ведь нужно знать, что там надо писать define, компилятор об это не говорит. Хорошо, допустим я знаю про это (я знал, выше писал, сработал паттерн), но я все равно не нашел подобным образом ничерта. Потому что искал по коду комплекса, где у меня развалилась сборка, а макрос этот злосчастный был во внешних зависимостях.
В данном случае это следует из того, что это имя много раз применялось. Но с тем же успехом этим именем могло быть и какое-нибудь «myVeeryLongVariableName». Я к чему говорю: мой ответ касался вашего замечания, что переменная плохо ищется потому, что у нее короткое имя — логично было предположить, что вы не знаете про grep, и/или регулярки.
Я к чему говорю: мой ответ касался вашего замечания, что переменная плохо ищется потому, что у нее короткое имя — логично было предположить, что вы не знаете про grep, и/или регулярки.
Но контекст-то нужно учитывать. Я сперва озвучил цифры не просто так. В контексте, который я описывал, именно короткое имя является причиной «плохого поиска» и именно поэтому, чтобы смочь составить регулярку, нужно знать достаточно о вариантах применения, дабы отразить это в выражении. Если варианты неизвестны, то поиск сильно затрудняется.
имя короткое и оно плохо ищетсяКстати, удивительно, что в IDE-программе, априори способной на синтаксический разбор кода, искать обычно можно лишь текст без возможности указания типа искомой сущности — например, имя переменной, имя функции или что-то ещё.
Этот код не должен скомпилироваться, те офлайн ошибка, да и код ошибки будет довольно понятным: ideone.com/c0svLX
Может, научить студентов определять корректные названия переменных, особенно глобальных?
Вариант с «#define» плох тем, что, если «задефайненная» константа используется, утрируя, в пятидесяти местах в коде — препроцессор понавставит ее напрямую в пятьдесят мест, и, я сомневаюсь, что позже, при оптимизации, компилятор будет искать одинаковые числа.
Когда же используется «const», компилятор видит, что это одна и та же переменная, и не станет ее дублировать 50 раз.
Когда же используется «const», компилятор видит, что это одна и та же переменная, и не станет ее дублировать 50 раз.
Не думаю, что даже при использовании в коде 50 раз одна float-переменная займет много места.
Вариант с констом займет еще больше, как описано в статье, если файл инклудится многократно.
Вариант с констом займет еще больше, как описано в статье, если файл инклудится многократно.
Разумное возражение. Но хочу заметить, что с другой стороны, при lto-оптимизации все дубликаты наверняка будут удалены, что крайне сомнительно в случае с «#define».
Там вообще не будет никакой переменной, зачем она?
Если посмотреть на это с чисто технической точки зрения, то использование #define для подстановки этих 50 float'ов выгоднее. Если код будет компилироваться для 32-х разрядной машины, то при использовании const во всех 50 местах будет подставлен указатель на константу, который будет занимать 4 байта, как и непосредственно сам float, т.е. разницы нет. Но если вы скомпилируете код под 64-х битную архитектуру, то указатели будут занимать не 4, а 8 байт, т.е. в 2 раза больше, чем непосредственно само значение типа float. Тогда выгоднее использовать #define.
Если посмотреть на это с чисто технической точки зрения, то использование #define для подстановки этих 50 float'ов выгоднее.Это с какого такого перепугу?
Если код будет компилироваться для 32-х разрядной машины, то при использовании const во всех 50 местах будет подставлен указатель на константу, который будет занимать 4 байта, как и непосредственно сам float, т.е. разницы нет. Но если вы скомпилируете код под 64-х битную архитектуру, то указатели будут занимать не 4, а 8 байт, т.е. в 2 раза больше, чем непосредственно само значение типа float.Это для какой-такой архитектуры, я извиняюсь? Нет, я верю, что вы можете придумать такую архитектуру, где всё будет так, как вы описали, но это уже какая-то Аристотельщина началась, когда тысячи лет люди спорят о том, сколько у мухи ног, но почему-то взять муху в руку и их посчитать никому в голову не приходит.
Я про это уже писал: практически на всех современных архитектурах у вас нет выбора — загружать ли константу из памяти через указатель или встроить её прямо внутрь команды. На RISC'ах её просто некуда встраивать — команды 32битные (иногда 16битные), а там, кроме константы ещё должен быть опкод, номер регистра, куда всё это грузить и прочее. В x86 таких ограничений нет, но и команд для загрузки
float
'ов из immediate
'а тоже нет.А вот что будет выгоднее — зависит от кучи обстоятельств. На x86 вариант с
define
ами проигрывает всегда — без вариантов. А вот на каком-нибудь ARM'е многое зависит от компилятора и от программы. Так как «большой» указатель в инструкцию может не влезть, то может получиться так, что при выносе константы в отдельный файл вам потребуется загружать указатель в какой-нибудь регистр отдельной командой. Такая странная конструкция может привести к тому, что будет выигрывать либо один вариант, либо другой в завимости от опций компиляции и структуры кода! Но уж никак не в зависимости от разрядности процессора — тут AArch32 от AArch64 почти не отличается.Примеры плохие. Если каждый будет вытаскивать лольканые константы частных фукций в заголовочные файлы — будет бред полный.
написать const double\float PI = 3.1415...; внутри ф-ии и все!
Ведь задача стоит в написании функции. Что является очень локальной задачей. И если при этом программист ради ф-ии в пару строк добавляет в проект новые глобальные константы да еще и много раз (в поиске мнимого идеала) меняет заголовочные файлы в проекте… печаль иметь такого учителя.
Те, как задачка для ума — забавно! Но делать это с позиции грамотного рабочего подхода к выполнению реальных задач — неприемлимо и учит плохому.
PS: Конечно все выше сказанное мое сугубо личное ИМХО, и было бы интересно узнать другие мненния ))
написать const double\float PI = 3.1415...; внутри ф-ии и все!
Ведь задача стоит в написании функции. Что является очень локальной задачей. И если при этом программист ради ф-ии в пару строк добавляет в проект новые глобальные константы да еще и много раз (в поиске мнимого идеала) меняет заголовочные файлы в проекте… печаль иметь такого учителя.
Те, как задачка для ума — забавно! Но делать это с позиции грамотного рабочего подхода к выполнению реальных задач — неприемлимо и учит плохому.
PS: Конечно все выше сказанное мое сугубо личное ИМХО, и было бы интересно узнать другие мненния ))
Студент: А, к черту все это! Лучше я стану бариста.
«Бариста должен знать температуру воды, давление в машине, то, как она работает, сколько граммов кофе нужно на чашку и с каким усилием его утрамбовывать, сколько секунд вода проходит через него. Количество кофе на чашку может меняться в зависимости от влажности, давления и температуры воздуха на улице. От этого зависит скорость прохождения воды и в конечном счете качество напитка.»
В общем те же фабереже, только в профиль :-)
Так ему уже хватит знаний что бы без заморочек написать С-говнокод под ардуинку для определения оптимального режима по датчикам и не будет париться ))
А потом выясняется, что именно сегодня ему достались зёрна с дерева, которое росло на чуть более солнечном склоне, чем остальные — и для них идеальные параметры чуть-чуть иные.
Думается мне, что все знания мира о заваривании кофе несколько меньше стандарта Си++.
И-эх, ждал разных методов вычисления числа Пи, от использования тригонометрических функций до численных методов и рандома, а топик оказался об особенностях использования констант и разных компилляторов.
Возможно, пропустил где-нибудь выше замечание… Но разве при работе с числами с плавающей точкой в принципе допустимо использование сравнений вроде CalcCircumference( r ) == CalcCircumference( r ), где CalcCircumference(...) возвращает float?
Вроде бы обычно используют сравнение с контролем точности до какого-то знака.
Вроде бы обычно используют сравнение с контролем точности до какого-то знака.
Скорее тут речь была о детерминированности результата. Вызывается 2 раза одна и та же ф-ия с одинаковыми (бинарно) входными параметрами.
Если можно, приведите код примера сравнения с контролем точности до какого-то знака на С++
bool compareFloat(float x, float y, int precision)
{
float multiplier = pow(10,precision);
return static_cast<int>(x * multiplier) == static_cast<int>(y * multiplier)
}
Чего-то мне кажется что вряд ли это ситуацию исправит. Проблемы с невозможность задать всякое число с плавающей точкой с помощью float (из-за привязки к степеням двойки) и с особенностями его аппаратного вычисления никуда не денутся, сколько не домножай его на другие числа.
Хотя, если вам такое решение помогло, интересно было бы почитать каким образом.
Хотя, если вам такое решение помогло, интересно было бы почитать каким образом.
Возводить 10 в степень и делать два умножения для сравнения двух чисел? Можно попрощаться с производительностью, которая у плавающей точки и так не очень, по сравнению с целыми числами. Лучше, как это принято, передавать некий эпсилон, с которым сравнивать разницу x и y:
bool compareFloat(float x, float y, float epsilon)
{
assert(epsilon > 0.0f);
return abs(x - y) < epsilon;
}
Авторский код можно переработать, убрав возведение в степень в шаблон, и сместив таким образом это дело на этап компиляции.
На этот же этап можно сместить проверку того, что программист не затребовал точности больше, чем есть у float.
На этот же этап можно сместить проверку того, что программист не затребовал точности больше, чем есть у float.
Только этот метод работает только для чисел около 0. Для любых x и y, вот более правильный метод: realtimecollisiondetection.net/blog/?p=89
Там куча проблем, взять хотя бы тот факт, что автор садит в одну формулу абсолютную и относительные погрешности. Как это обосновать? У нас из математического определения алгоритма следует, например, что цикл нужно остановить, когда абсолютная разница станет меньше эпсилон. А автор бабахает туда еще и относительную погрешность, причем без математического обоснования. А если по относительной погрешности все сошлось, а по абсолютной — еще нет, то есть определение алгоритма не выполнено?
Также замечу, что все численные алгоритмы уже внутри своего построения обеспечивают «значения около нуля» (обезразмеривание задачи) и учитывают погрешности при вычислениях, так что нет необходимости в применении данных фокусов — нужно правильно строить алгоритм.
Также замечу, что все численные алгоритмы уже внутри своего построения обеспечивают «значения около нуля» (обезразмеривание задачи) и учитывают погрешности при вычислениях, так что нет необходимости в применении данных фокусов — нужно правильно строить алгоритм.
Там куча проблем, взять хотя бы тот факт, что автор садит в одну формулу абсолютную и относительные погрешности. Как это обосновать? У нас из математического определения алгоритма следует, например, что цикл нужно остановить, когда абсолютная разница станет меньше эпсилон. А автор бабахает туда еще и относительную погрешность, причем без математического обоснования. А если по относительной погрешности все сошлось, а по абсолютной — еще нет, то есть определение алгоритма не выполнено?Там написано почему: потому что сравнения с абсолютной и относительной погрешностями взаимоисключительно не работают на величинах разных порядков.
Также замечу, что все численные алгоритмы уже внутри своего построения обеспечивают «значения около нуля» (обезразмеривание задачи) и учитывают погрешности при вычислениях, так что нет необходимости в применении данных фокусов — нужно правильно строить алгоритм.Чушь какая-то.
Очень просто — сравнивать нужно число знаков в двоичной системе счисления
Потом ответ можно «привести» к варианту для десятичной системы, аккуратно воспользовавшись школьными теоремами о логарифмах и смене основания.
- Разбить число на мантиссу и экспоненту
- Сравнить экспоненты, совали — продолжаем, не совпали — ответ готов — 0 знаков.
- Сравнить знаки мантисс, не совпали — ответ готов, 0 знаков.
- Сравнить цифры мантисс, начиная с младших разрядов, нумерация с 0. Номер первого несовпадения и есть ответ.
Потом ответ можно «привести» к варианту для десятичной системы, аккуратно воспользовавшись школьными теоремами о логарифмах и смене основания.
Так тоже не получится. Тут проблема глубже. Дело в том, что, помимо погрешностей операций над числами с плавающей точкой на одном процессоре, на разных процессорах могут результаты тоже разные получаться. И если сравнивать через оператор равенства числа, получение с разных компьютеров (например, для мультиплеера), то при одних и тех же операндах могут разные результаты выходить.
Кстати, то, что вы предлагаете, если я правильно понял, это обычное сравнение — просто зачем-то очень сложное, ещё и зависимое от способа запаковки числа с плавающей точкой.
Кстати, то, что вы предлагаете, если я правильно понял, это обычное сравнение — просто зачем-то очень сложное, ещё и зависимое от способа запаковки числа с плавающей точкой.
Ваше замечание очень полезно. Стоит однако учесть, что IEEE 754-2008, рекомендует изготовителям компиляторов и процессоров реализовывать арифметику так, чтобы результаты были воспроизводимы хотя бы в пределах одного языка.
У меня не «обычное» сравнение — оно сообщает, начиная с какого двоичного знака числа начинают различаться. Оно не привязано к способу упаковки числа — в С есть стандартные функции (fprec) для изъятия мантиссы и экспоненты, для которых в дальнейшем возможен перевод в целый тип.
У меня не «обычное» сравнение — оно сообщает, начиная с какого двоичного знака числа начинают различаться. Оно не привязано к способу упаковки числа — в С есть стандартные функции (fprec) для изъятия мантиссы и экспоненты, для которых в дальнейшем возможен перевод в целый тип.
оно сообщает, начиная с какого двоичного знака числа начинают различаться
Да, извиниюсь, невнимательно прочитал. Теперь понял, что вы предлагаете таким образом получать момент первого расхождения, а не сравнивать числа. Тогда нормальное решение, да.
Оно не привязано к способу упаковки числа — в С есть стандартные функции
Ух ты, слышал про такую возможность, но никогда не пользовался. Спасибо.
А не будет ли проблем вблизи степеней двойки? Ведь у чисел 0.999999 и 1.000001 экспоненты разные. Так что на втором шаге вы получите «не равны совсем».
Лучше уж как-нибудь так:
Вернёт относительную погрешность, умноженную примерно на 2^24 (для разных пар коэффициенты будут разными, но различаются они не более, чем вдвое). Не будет работать в окрестности нуля (особенно, когда числа разных знаков).
Лучше уж как-нибудь так:
int Diff(float a,float b){
int ia=*(int*)&a,ib=*(int*)&b;
return abs(ia-ib);
}
Вернёт относительную погрешность, умноженную примерно на 2^24 (для разных пар коэффициенты будут разными, но различаются они не более, чем вдвое). Не будет работать в окрестности нуля (особенно, когда числа разных знаков).
Если мы сравниваем два значения, полученные, скажем так, одинаковым путем, то лучше всего сравнивать модуль разности чисел с погрешностью, определенной для данного типа в стандартной библиотеки:
bool areSame(float a, float b)
{
return std::fabs(a - b) <= std::numeric_limits<float>::epsilon()
}
Трудно быть С++ программистом. Спасибо за статью. То, что компилятор не инлайнит константы с плавающей запятой и клонирует их, на самом деле, стало для меня сюрпризом.
А это потому, что вы, в своё время не обратили внимание на то, как в процессоре реализована работа с плавучкой. На большинстве процессоров целочисленную константу можно встроить как
immediate
внутрь инструкции, а float
или double
— нельзя. Соответственно самый оптимальный способ — это оставить константу в покое и загрузить её из памяти.Просто csc тот же инлайнит все константы, которые встречает, заменяя на численное значение. Константа может быть любым числом или строкой. Я был уверен, что в плюсах поведение такое же, ибо они славятся именно миллиардом и одной оптимизацией на этапе компиляции всего, что только возможно.
Ну дык свойства байткода и CPU-кода несколько разные. То, что хорошо ложится на байткод может плохо ложиться на CPU и наоборот. Те же константы с плавучкой могут-таки быть, например, на ARMе встроены в инструкцию — но только в огромным количеством ограничений, бóльшая же часть всё равно грузится из памяти.
Ну может вы и правы.
Но для справки, такой код:
компилятор преобразует в
Но для справки, такой код:
private const float PI = (float) Math.PI;
private static float Foo(float d)
{
return d * PI;
}
компилятор преобразует в
.method private hidebysig static float32
Foo(float32 d) cil managed
{
// Размер кода: 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.r4 3.1415927
IL_0006: mul
IL_0007: ret
} // end of method Test::Foo
Потому что IL по степени высокоуровневости примерно как тот же C (а местами и куда повыше). Там надо смотреть, что потом сгенерит JIT из этого байткода.
Заголовок меня смущает, все-таки. Неясно, что значит вычислить окружность.
Правильнее было бы вычислить «длину окружности», как в тексте статьи.
Правильнее было бы вычислить «длину окружности», как в тексте статьи.
НЛО прилетело и опубликовало эту надпись здесь
acos(-1) даст вам желаемое PI в нужном типе.
Вычислять арккосинус каждый раз когда надо использовать Пи?
Это… Хм. Даже не знаю как назвать.
Это… Хм. Даже не знаю как назвать.
Если это C++11 и арккосинус реализован и помечен как
constexpr
— почему бы и нет?www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-manual-325462.pdf (осторожно, 3.5к+ страниц)
www.agner.org/optimize/instruction_tables.pdf
160 тиков на FPATAN. Да, отзываю.
Кстати, насчёт «пи» у интела много написано. Цитирую:
www.agner.org/optimize/instruction_tables.pdf
160 тиков на FPATAN. Да, отзываю.
Кстати, насчёт «пи» у интела много написано. Цитирую:
8.3.8 Approximation of Pi
When the argument (source operand) of a trigonometric function is within the domain of the function, the argu-
ment is automatically reduced by the appropriate multiple of 2π through the same reduction mechanism used by
the FPREM and FPREM1 instructions. The internal value of π (3.1415926...) that the x87 FPU uses for argument
reduction and other computations, denoted as Pi in the expression below. The numerical value of Pi can be written
as:
Pi = 0.f ∗ 2 2
where the fraction f is expressed in binary form as:
f = C90FDAA2 2168C234 C
(The spaces in the fraction above indicate 32-bit boundaries.)
The internal approximation Pi of the value π has a 66 significant bits. Since the exact value of π represented in
binary has the next 3 bits equal to 0, it means that Pi is the value of π rounded to nearest-even to 68 bits, and also
the value of π rounded toward zero (truncated) to 69 bits.
However, accuracy problems may arise because this relatively short finite approximation Pi of the number π is used
for calculating the reduced argument of the trigonometric function approximations in the implementations of FSIN,
FCOS, FSINCOS, and FPTAN. Alternately, this means that FSIN (x), FCOS (x), and FPTAN (x) are really approxi-
mating the mathematical functions sin (x * π /Pi), cos (x * π / Pi), and tan (x * π / Pi), and not exactly sin (x), cos
(x), and tan (x). (Note that FSINCOS is the equivalent of FSIN and FCOS combined together). The period of sin (x
* π /Pi) for example is 2* Pi, and not 2π.
See also Section 8.3.10, “Transcendental Instruction Accuracy” for more information on the accuracy of these func-
tions.
FLDPI
Упс, а слона-то я в этой PDF'ке и не заметил. Да, самая разумная инструкция из всего, что обслуждалось.
Есть только одна проблема: на современных системах вычисления лучше проводить не с использованием x87 инструкций, а с использованием SSE инструкций. А единственный способ «перегнать» данные из ST(x) в XMMx — через память. Так что смысла в FLDPI, увы, нету никакого.
P.S. ATAN нонче тоже считают через полиномы, а не через FATAN2…
P.S. ATAN нонче тоже считают через полиномы, а не через FATAN2…
Говорят, windows 10 будет занимать на 4.5 Гб меньше предыдущей версии.
То есть на Си таких граблей не будет?
Тема с дублированием констант напомнила о похожей проблеме в Java. Если вы объявляете строковую константу (static final String) в классе и ссылаетесь на неё из другого класса, при компиляции она копируется в другой класс полностью. Если константа большая, ваш код может существенно вырасти в размере. Так однажды исправление подобной проблемы (инициализацию констант выполнили чуть позже) уменьшило размер стандартной библиотеки на 1Мб (это с учётом зипования).
лучше присвоить константе имя и поместить в заголовочный файл.
Имя присвоить — можно. Но в заголовочный файл — зачем? Это нужно только в случаях, если ставится задача обеспечить видимость константы в других частях программы. Но исходная задача, которая была поставлена — вычислить длину окружности. Мало ли, какие там константы используются?
Если бы студента попросили вычислить функцию Бесселя первого рода — то в алгоритмах ее вычисления тоже используются константы. Это «квадратные» числа, и вряд ли они могут понадобиться еще где-то, кроме как в дебрях алгоритма вычисления функции. Их что — тоже экспортировать?
Все эти пляски с бубном из-за константы — Overengineering в чистом виде. Конечно, учить сложным и профессиональным методам как-то нужно, но применение их в простых случаях — это стрельба из пушки по воробьям. Некоторые наивные программисты, когда их научат такому, и тратят час на написание программы вычисления длины окружности. Вместо того, чтобы просто решить задачу адекватными средствами исходя из двух критериев: 1) получить нужный результат; 2) получить его быстро.
Как насчет удаления констант в пользу статической константной функции, помеченной inline?
struct globals
{
static inline float gimmePi() const
{
return 9000; //hove a nice debug, lol
}
};
Я думаю, тут лучше бы подошли Variable templates.
Это если в проекте доступна такая роскошь, как C++11.
//у меня, кстати, там const лишний. Метод уже и так статический.
//у меня, кстати, там const лишний. Метод уже и так статический.
Да и структура возможно лишняя, достаточно пространства имен.
НЛО прилетело и опубликовало эту надпись здесь
Сцена вторая мне уже не понравилась! Нефиг свои константы выдумывать!
Студент: Как вам такой вариант?
Преподаватель: Да, этот код может нормально откомпилироваться. А может и нет. M_PI не определена в стандартах C или C++… (overquote)
Студент: да нет проблем!
Студент: Как вам такой вариант?
#include <math.h>
float CalcCircumference1(float d)
{
return d * M_PI;
}
Преподаватель: Да, этот код может нормально откомпилироваться. А может и нет. M_PI не определена в стандартах C или C++… (overquote)
Студент: да нет проблем!
#define _USE_MATH_DEFINES
#include <math.h>
#ifndef M_PI // support weird compilers
#define M_PI 3.14159265358979323846
#endif
float CalcCircumference1(float d)
{
return d * float(M_PI);
}
Согласен, только не
float(M_PI)
, а определить и использовать M_PI_F
.Я думаю, что комментарий "// support weird compilers" нужно-таки перенести к "#define _USE_MATH_DEFINES", а остальное — просто убрать. Потому как M_PI — это всё-таки стандартная константа, а единственная платформа, которая человеческие стадарты не соблюдает и с которой всё-таки приходится общаться — это Windows.
Я знаю, что стандартная. Я действовал в системе координат того преподавателя, который ставит гипотетические условия «а вот не все компиляторы поддерживают, что вы будете делать?»
Естественно в реальной практике о #ifndef M_PI не может быть речи.
Естественно в реальной практике о #ifndef M_PI не может быть речи.
В C99 этой константы нет. Так что это какой‐то местечковый стандарт вроде POSIX. Просто настолько удобный, что константу скопировали все адекватные libc и компиляторы. Кстати, glibc на моей системе спрятала M_PI под
Насчёт практики: github.com/search?q=_ISOC99_SOURCE&ref=searchresults&type=Code&utf8=%E2%9C%93: более 15 тысяч результатов с языком C, более 5 с C++, всего более 40 тысяч с учётом других языков (генераторы?). А ведь есть ещё другие константы с аналогичным эффектом. Так что может, и ещё как.
defined(__USE_MISC) || defined(__USE_XOPEN)
. Что‐то из этого определено по‐умолчанию, но, к примеру, такой код не скомпилируется:#define _ISOC99_SOURCE
#include <math.h>
#include <stdio.h>
int main(int argc, char **argv, char **environ)
{
printf("%lf", M_PI);
return 0;
}
Насчёт практики: github.com/search?q=_ISOC99_SOURCE&ref=searchresults&type=Code&utf8=%E2%9C%93: более 15 тысяч результатов с языком C, более 5 с C++, всего более 40 тысяч с учётом других языков (генераторы?). А ведь есть ещё другие константы с аналогичным эффектом. Так что может, и ещё как.
я тут наткнулся на ваш коммент, и решил посмотреть те результаты. Они меня сперва насмешили, потому что там похоже все сгенерено автоматически (ну по большей части).
Но я решил подумать еще и посмотрел github.com/search?utf8=%E2%9C%93&q=define+M_PI&type=Code&ref=searchresults
И тут я уже заплакал… сотни тысяч M_PI с разной точностью…
Но я решил подумать еще и посмотрел github.com/search?utf8=%E2%9C%93&q=define+M_PI&type=Code&ref=searchresults
И тут я уже заплакал… сотни тысяч M_PI с разной точностью…
А вот это вообще десятилетие гуляет:
#define M_PI 3.14159263
и никого не волнует…
#define M_PI 3.14159263
и никого не волнует…
Все ждал, на каком шаге будет проверка диаметра на неотрицательность, но пост не об этом.
И похоже, в своих проектах нужно на constexpr перебираться.
И похоже, в своих проектах нужно на constexpr перебираться.
const float sinTable[1024] = { 0.0, 0.1, };«Таблица грехов»? :)
У компилятора в VC++ 2013 Update 2 появился параметр /Gw, который помещает каждую глобальную переменную в отдельный контейнер COMDAT, позволяя компоновщику выявлять и избавляться от дубликатов.Если я правильно понял, это аналог связки флагов gcc -fdata-sections и ld --gc-sections, которые очень любят в embedded (обычно с gcc -ffunction-sections).
Тем, кто захочет почитать об этом флаге в MSDN: не читайте русскую версию, если хотите что-то понять.
компоновщик использует общей оптимизации программы для сравнения разделы СOMDAT через несколько объектных файлов объекта
Можно также использовать /OPT: Брандмауэр подключения к интернету и компоновщика /LTCG и слияния в исполняемом файле любые идентичные только для чтения глобальных данных через несколько объектных файлов объекта компилировали с параметром /Gw.
Тем, кто ставит автоперевод, да еще и на технические сайты, видимо не дает покоя слава знаменитых гуртовщиков мыши.
Особую эффективность команда «Послать на ...» приобретет при передачи
посланий через Е-почту и общение с вашими коллегами и друзьями в местной
сети-работе. Попробуйте мощь команды «Послать на ...», и вы быстро
убедитесь, что без нее трудно существовать под Окнами 95.
Пишите нам и помните, что Microsoft компания всегда думает о том, как
вас лучше сделать.
Все примеры начиная со «сцены четвёртой» — некорректны, поскольку нам дали задание написать ОДНУ ФУНКЦИЮ, а прав замусоривать глобальное пространство имён своими фантазиями — никто не давал. Если Пи будет нужно и другим функциям — мы вынесем его, как только получим задание их реализовать.
Вторая мысль в том, что мало кто программирует «на стандарте», обычно люди программируют всё-же под какой-то один или несколько компиляторов, а все они, так или иначе, имеют своё определение Пи — вот его и нужно использовать, а не плодить сущности. Вариант mapron — самый близкий к идеалу.
Вторая мысль в том, что мало кто программирует «на стандарте», обычно люди программируют всё-же под какой-то один или несколько компиляторов, а все они, так или иначе, имеют своё определение Пи — вот его и нужно использовать, а не плодить сущности. Вариант mapron — самый близкий к идеалу.
А чем плох дефайн?
И интересно, как правильно писать под микроконтроллеры — там эффективность очень важна…
И интересно, как правильно писать под микроконтроллеры — там эффективность очень важна…
А почему нельзя
? Так как это const'ы — они должны сколлапситься в момент трансляции.
const long PI = 31415926535;
const long PIFRAC = 10000000000;
float CalcCircumference1(float d)
{
return d * 1.0 * PI / PIFRAC;
}
? Так как это const'ы — они должны сколлапситься в момент трансляции.
Как вариант:
calc_pi.h:
calc_pi.c:
calc_pi.h:
const float getPi(void);
float CalcCircumference(float d);
calc_pi.c:
const float getPi(void)
{
return 3.14159f;
}
float CalcCircumference(float d)
{
return d*getPi();
}
Забавно, что никто так и не вспомнил, что длина окружности — это 2*pi*r.
Прошу прощения, но мне показалось, что вместо решения поставленной задачи бедный студент борется с особенностями ЯП и разных его реализаций (при всем уважении к С++)… ИМХО.
А потом заказчик вместе с начальником отдела разработок ломают голову: почему программисты 3 месяца решают задачу, которая решается за 3 дня?
Ответ: потому что вместо решения поставленной ПРИКЛАДНОЙ задачи они (программисты) думают, например, над тем, куда число pi засунуть, чтобы как в ВУЗе учили… (утрирую). Когда программу пишешь — нужно над решением поставленной задачи думать, а не с компилятором бороться и самовыражаться. Заказчику наплевать куда это pi засунете — ему продукт нужен, чтобы вовремя и работал. ИМХО, одним из минусов С++ в обучении программированию и выступает то, что вместо того, чтобы решать задачу, бедный неподготовленный студент борется с языком… Можно кидать в меня помидоры, но это мое мнение… А статья — показательная и интересная. Спасибо Автору!
А потом заказчик вместе с начальником отдела разработок ломают голову: почему программисты 3 месяца решают задачу, которая решается за 3 дня?
Ответ: потому что вместо решения поставленной ПРИКЛАДНОЙ задачи они (программисты) думают, например, над тем, куда число pi засунуть, чтобы как в ВУЗе учили… (утрирую). Когда программу пишешь — нужно над решением поставленной задачи думать, а не с компилятором бороться и самовыражаться. Заказчику наплевать куда это pi засунете — ему продукт нужен, чтобы вовремя и работал. ИМХО, одним из минусов С++ в обучении программированию и выступает то, что вместо того, чтобы решать задачу, бедный неподготовленный студент борется с языком… Можно кидать в меня помидоры, но это мое мнение… А статья — показательная и интересная. Спасибо Автору!
В реальном проекте такой вызов написали бы максимально простым, а потом исправляли бы в случаи возникновения проблем. И такой подход, как на меня, правильный.
> Когда программу пишешь — нужно над решением поставленной задачи думать, а не с компилятором бороться и самовыражаться.
А в итоге получается MMO, у которой интерфейс написан на флэше и жрёт почти все ресурсы проца, и в итоге даже на относительно мощных системах GPU загружен меньше чем наполовину, а FPS до 20 с трудом поднимается.
А в итоге получается MMO, у которой интерфейс написан на флэше и жрёт почти все ресурсы проца, и в итоге даже на относительно мощных системах GPU загружен меньше чем наполовину, а FPS до 20 с трудом поднимается.
Число Пи здесь указано недостаточно точно, но дело в том, что целочисленные константы в заголовочных файлах не требуют выделения памяти в отличие от остальных констант
Не требуют выделения памяти до тех пор, пока кто-то не возьмет адрес у такой константы:
const int pi_i = 3;
const int* n = &pi_i;
илиvoid foo(const int* n);
foo(&pi_i);
Компилятор может по возможности не выделять память под константу, а не обязан это делать всегда.
Я на 100% не уверен, но, кажется, в C++11 уже обязан. Это сделали ради классов: раньше описав такую константу в классе в
.h
её приходилось описывать ещё и в .cc
файле даже если её адрес никогда явно не брался. Сейчас — в стандарте появилось понятие «Integral constant expression», которое компилятор обязан вычислять в compile-time.Понятие integral constant expression в стандарте было с самого начала, и статический (явно или неявно) const int с соответствующим инициализатором под него тоже всегда подпадал. Но это все перпендикулярно тому, выделяется ли память под переменную. Т.е. если у вас написано что-то вот такое:
То компилятор обязан это съесть, хотя размер массива обязан быть константой времени компиляции. Но при этом он имеет полное право выделить память под n как переменную.
>> раньше описав такую константу в классе в .h её приходилось описывать ещё и в .cc файле даже если её адрес никогда явно не брался.
Для констант, у которых инициализатором является integral constant expression, их всегда можно было определять в теле класса вместе с инициализатором, а определять их снаружи требовалось только тогда, когда берется их адрес.
const int n = 0;
int a[n];
То компилятор обязан это съесть, хотя размер массива обязан быть константой времени компиляции. Но при этом он имеет полное право выделить память под n как переменную.
>> раньше описав такую константу в классе в .h её приходилось описывать ещё и в .cc файле даже если её адрес никогда явно не брался.
Для констант, у которых инициализатором является integral constant expression, их всегда можно было определять в теле класса вместе с инициализатором, а определять их снаружи требовалось только тогда, когда берется их адрес.
Размер массива уже лет 16 (с C99) не обязан быть константой времени компиляции. Убедитесь сами.
Тут речь, вообще-то, о плюсах, в которых нет VLA. То, что gcc компилирует ваш код — это следствие того, что он поддерживает сишные VLA в плюсах как языковое расширение, но по стандарту это ошибка. Кстати, со времен C99 появился еще C11, в котором эта фича сделана conditionally supported, т.е. не все компиляторы обязаны ее поддерживать.
Впрочем, в моем оригинальном примере подразумевалось, что этот код не в функции. В этом случае константа таки требуется даже и в С99, VLA там допустим только для локальных переменных.
Впрочем, в моем оригинальном примере подразумевалось, что этот код не в функции. В этом случае константа таки требуется даже и в С99, VLA там допустим только для локальных переменных.
Спасибо. Покажите пожалуйста параграф/раздел стандарта C++, в котором явно написано, что относительно таких массивов нет обратной совместимости со стандартом C.
Такого параграфа нет, поскольку C++ отпочковался от C до появления стандарта C99 (в 98-м году). Поэтому в соответствующем разделе стандарта (C.1[diff.iso]) описывается разница с C89.
Впрочем, это не суть важно — стандарт плюсов вообще практически ничего не определяет в терминах «а вот это как в C», за исключением стандартной библиотеки, и весь раздел C в нем носит не нормативный, а информативный характер. Поэтому конкретно вопрос с объявлением массивов решается чтением соответствующего параграфа стандарта, а именно 8.3.4[dcl.array], в котором в первой же строчке дан синтаксис соответствующего декларатора:
И там же дальше:
Впрочем, это не суть важно — стандарт плюсов вообще практически ничего не определяет в терминах «а вот это как в C», за исключением стандартной библиотеки, и весь раздел C в нем носит не нормативный, а информативный характер. Поэтому конкретно вопрос с объявлением массивов решается чтением соответствующего параграфа стандарта, а именно 8.3.4[dcl.array], в котором в первой же строчке дан синтаксис соответствующего декларатора:
In a declaration T D where D has the form
D1 [ constant-expressionopt] attribute-specifier-seqopt
И там же дальше:
If the constant-expression (5.19) is present, it shall be a converted constant expression of type std::size_t and its value shall be greater than zero.
Спасибо. Очень жаль, что я ошибся насчет массивов и вызвал эту дискуссию.
Очень жаль, что вас за это минусуют зачем-то. Это, мягко говоря, неочевидная вещь, про которую в книгах обычно не прочитаешь, компиляторы могут расширять стандарт (и более того, если они и так реализуют C99, то им легче здесь допустить расширение, чтобы не держать разную логику для C и C++), а сам стандарт C++14 — книга на 1300 страниц, которую читают, в основном, авторы компиляторов и библиотек в Boost, ну и language lawyers :)
Вообще-то это вещь, про которую пишет даже Wikipedia:
Parts of the C99 standard are included in the current version of the C++ standard, C++11, including integer types, headers, and library functions. Variable-length arrays are not among these included parts because C++'s Standard Template Library already includes similar functionality.
Простите, но мое чувство справедливости не дает проигнорировать этот момент.
Дело в том, что уже есть, правда называются они не VLA (см. цитаты). Тот же параграф, что вы цитировали выше, в новом стандарте претерпел изменения:
и ниже:
Короче говоря неконстанту в размере массива писать теперь официально можно и это не ошибка.
Тут речь, вообще-то, о плюсах, в которых нет VLA.
Дело в том, что уже есть, правда называются они не VLA (см. цитаты). Тот же параграф, что вы цитировали выше, в новом стандарте претерпел изменения:
In a declaration T D where D has the form
D1 [ expressionopt] attribute-specifier-seqopt
и ниже:
If the expression, after converting to std::size_t, is a core constant expression whose value is N, the
type of the identifier of D is “derived-declarator-type-list array of N T”.
Otherwise, the type of the identifier of D is “derived-declarator-type-list array of runtime bound of T” and
the value of the expression designates the number of elements N in the array.
Короче говоря неконстанту в размере массива писать теперь официально можно и это не ошибка.
Это черновик четырнадцатого стандарта?
В каком именно новом стандарте? Я цитировал C++14, это актуальная на данный момент версия стандарта. То, что вы привели — это из текущего черновика C++17?
Понятно.
Кому интересно — там вообще довольно забавная история. Иметь VLA в какой-то форме (т.е. по сути — типобезопасный и стандартизированный alloca) всем хочется, но представления о том, как он должен выглядеть, были разные. Одна группа проталкивала уже существующий в C99 вариант, с основной мотивацией в виде совместимости с C, но он изрядно усложняет язык, поэтому их выпилили.
С другой стороны был вариант с std::dynarray, который определен как библиотечный класс, но с таким интерфейсом, чтобы компилятор по факту мог сделать хитрооптимизированную реализацию с выделением на стеке (а мог и не делать — т.е. это QoI, а не требование). Его тоже сначала сгоряча добавили в C++14 (самое забавное, что в стандарте некоторое время были обе эти вещи), а потом выпилили на более поздних этапах, но не окончательно, а с выносом в отдельный Arrays TS. Так что в C++17 он, видимо, будет именно в таком виде.
Кому интересно — там вообще довольно забавная история. Иметь VLA в какой-то форме (т.е. по сути — типобезопасный и стандартизированный alloca) всем хочется, но представления о том, как он должен выглядеть, были разные. Одна группа проталкивала уже существующий в C99 вариант, с основной мотивацией в виде совместимости с C, но он изрядно усложняет язык, поэтому их выпилили.
С другой стороны был вариант с std::dynarray, который определен как библиотечный класс, но с таким интерфейсом, чтобы компилятор по факту мог сделать хитрооптимизированную реализацию с выделением на стеке (а мог и не делать — т.е. это QoI, а не требование). Его тоже сначала сгоряча добавили в C++14 (самое забавное, что в стандарте некоторое время были обе эти вещи), а потом выпилили на более поздних этапах, но не окончательно, а с выносом в отдельный Arrays TS. Так что в C++17 он, видимо, будет именно в таком виде.
Где почитать подробнее про то, какие константы требуют выделения памяти, а какие нет?
Что-то мне подсказывает, что если в наборе команд исполнителя (процессора или виртуальной машины) есть команды загрузки аргумента команды в регистр, то константа попадает сразу «в код». Если нет — то в сегмент данных и оттуда достается по оффсету/индексу. Ну то есть на ваш вопрос нет однозначного ответа, если вы не указываете на чем работаете.
Если говорить о стандарте, то это регламентируется пунктом 3.2 One definition rule. [basic.def.odr]
А именно, там определяется, какие переменные, функции и т.д. называются odr-used. В целом, если компилятору нужен адрес этой переменной/функции, то она odr-used. Далее сказано, что odr-used переменная должна быть определена в каждой единице трансляции.
О переменных:
То есть чтобы переменная не была odr-used, необходимо, чтобы она либо использовалась в невычисляемом контексте, либо попадала под ограничения константного выражения с немедленным применением к нему lvalue-to-rvalue преобразования.
Требования для константных выражений указаны (5.19 Constant expressions [expr.const]).
В С++03 константными могли быть только целые выражения, или с плавающей точкой, только если они сразу преобразуются к целым:
В C++11 ограничение для чисел с плавающей точкой было снято, и теперь литералы с плавающей точкой считаются константными выражениями (хотя стандарт оговаривает, что вычисления на этапе компиляции могут давать другой результат, чем в рантайме), и значит не обязаны быть определены в каждой единице трансляции.
А именно, там определяется, какие переменные, функции и т.д. называются odr-used. В целом, если компилятору нужен адрес этой переменной/функции, то она odr-used. Далее сказано, что odr-used переменная должна быть определена в каждой единице трансляции.
О переменных:
A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an
object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue
conversion (4.1) is immediately applied.
То есть чтобы переменная не была odr-used, необходимо, чтобы она либо использовалась в невычисляемом контексте, либо попадала под ограничения константного выражения с немедленным применением к нему lvalue-to-rvalue преобразования.
Требования для константных выражений указаны (5.19 Constant expressions [expr.const]).
В С++03 константными могли быть только целые выражения, или с плавающей точкой, только если они сразу преобразуются к целым:
An integral constant-expression can involve only literals (2.13), enumerators, const variables or static
data members of integral or enumeration types initialized with constant expressions (8.5), non-type tem-
plate parameters of integral or enumeration types, and sizeof expressions. Floating literals (2.13.3) can
appear only if they are cast to integral or enumeration types. Only type conversions to integral or enumera-
tion types can be used. In particular, except in sizeof expressions, functions, class objects, pointers, or
references shall not be used, and assignment, increment, decrement, function-call, or comma operators shall
not be used.
В C++11 ограничение для чисел с плавающей точкой было снято, и теперь литералы с плавающей точкой считаются константными выражениями (хотя стандарт оговаривает, что вычисления на этапе компиляции могут давать другой результат, чем в рантайме), и значит не обязаны быть определены в каждой единице трансляции.
А ещё можно сделать вот так:
По моему опыту, это зачастую наиболее простое и переносимое решение данной проблемы.
#include <boost/math/constants/constants.hpp>
float CalcCircumference1(float r)
{
using namespace boost::math::float_constants;
return 2.0f * pi * r;
}
По моему опыту, это зачастую наиболее простое и переносимое решение данной проблемы.
Вы смеётесь? Простое? Переносимое?
Потом вас попросят этот «хеллоциркле» перенести на какой-нибудь калькулятор — вы с бустом будете танцы устраивать?
Статья не о том, что есть «единственно правильный вариант».
Статья о том, что даже в простейших с виду вещах могут быть такие вещи, о которых новичок не предполагает, а гуру набил себе шишки.
Потом вас попросят этот «хеллоциркле» перенести на какой-нибудь калькулятор — вы с бустом будете танцы устраивать?
Статья не о том, что есть «единственно правильный вариант».
Статья о том, что даже в простейших с виду вещах могут быть такие вещи, о которых новичок не предполагает, а гуру набил себе шишки.
Это все происходит потому, что преподаватель при постановке задачи озвучил меньше требований, чем предполагал. На эту тему даже был анекдот про прапрщика и воду с газом или без газа. Конечно, возможен вариант, что некоторые требования подразумевались актуальными по умолчанию, а мы тут просто видим вырваную из контекста беседу.
Надо было сразу сказать: хочу чтобы оно компилилось на всем что можно, и занимало как можно меньше памяти. Возможно даже надо указать конкретные платформы, наборы библиотек, компиляторы и ключи запуска. Или может стоит оставить ключи запуска на усмотрение студента. Потому что необязательно требования такие всегда. Например, на спектруме применял следующий прием: угол два пи это 256 «градусов», и вычисляем синус по табличке. Работало очень быстро, но в памяти надо было держать эту таблицу.
Опять же, если изначально стоит задача написать приложение под конкретную платформу, и там уже есть нужные либы — то зачем повторять их функционал? А если все равно повторяешь их функционал — зачем тогда вообще использовать либы? Могут быть случаи где этому есть разумное обьяснение, а могут быть случаи, где нет.
Надо было сразу сказать: хочу чтобы оно компилилось на всем что можно, и занимало как можно меньше памяти. Возможно даже надо указать конкретные платформы, наборы библиотек, компиляторы и ключи запуска. Или может стоит оставить ключи запуска на усмотрение студента. Потому что необязательно требования такие всегда. Например, на спектруме применял следующий прием: угол два пи это 256 «градусов», и вычисляем синус по табличке. Работало очень быстро, но в памяти надо было держать эту таблицу.
Опять же, если изначально стоит задача написать приложение под конкретную платформу, и там уже есть нужные либы — то зачем повторять их функционал? А если все равно повторяешь их функционал — зачем тогда вообще использовать либы? Могут быть случаи где этому есть разумное обьяснение, а могут быть случаи, где нет.
Можно, конечно, на курсах заниматься такой ерундой и так и не дойти до реальных программ, погрязнув в поисках идеального определения константы. А можно ограничиться первым или вторым вариантом и продожить знакомиться с чем то более интересным и полезным.
НЛО прилетело и опубликовало эту надпись здесь
Если бы ко мне так придерались на собеседовании, то были бы посланы на____. Я пишу, как хочу, преобразую типы где надо и не надо, вставляю непонятные константы посредине кода, а над совместимостью компиляторов пусть думают создатели компиляторов.
Интересно почему ПИ округлили до 3. Надо округлять до 4, ведь это степень двойки! XD
const int pi_i = 3;Неправильно ейто.
#ifdef STATE_INDIANA const int pi_i = 4; #else const int pi_i = 3; #endif
Задача решена первым же вариантом студента.
В формулировке нету ни слова про версии компилятора, эффективность и прочие дополнительные условия, появляющиеся по ходу решения.
В формулировке нету ни слова про версии компилятора, эффективность и прочие дополнительные условия, появляющиеся по ходу решения.
«Пожалуйста, напишите на C++ функцию, которая получает диаметр круга как float и возвращает длину окружности как float»
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Вычислите длину окружности