Вычислите длину окружности

https://randomascii.wordpress.com/2014/06/26/please-calculate-this-circles-circumference/
  • Перевод
«Пожалуйста, напишите на C++ функцию, которая получает диаметр круга как float и возвращает длину окружности как float».

Звучит как задание на первой неделе курса по C++. Но это только на первый взгляд. Сложности возникают уже на первых этапах решения задачи. Предлагаю рассмотреть несколько подходов.

Студент: Как вам такой вариант?

#include <math.h>
float CalcCircumference1(float d)
{
    return d * M_PI;
}

Преподаватель: Да, этот код может нормально откомпилироваться. А может и нет. M_PI не определена в стандартах C или C++. С компилятором в VC++ 2005 это сработает, но для более поздних версий придется использовать #define _USE_MATH_DEFINES перед включением math.h, чтобы получить доступ к этой нестандартной константе. Причем в результате вы напишете код, с которым могут не справиться другие компиляторы.

Сцена вторая


Студент: Благодарю за мудрость, учитель. Я убрал зависимость от нестандартной константы M_PI. Так лучше?

float CalcCircumference2(float d)
{
    return d * 3.14159265358979323846;
}

Преподаватель: Да, так лучше. Этот код будет скомпилирован, и вы получите искомый результат. Но ваш код неэффективен. Вы умножаете число с одинарной точностью на константу с двойной точностью. Компилятору придется привести параметр функции типа float к типу double, а затем выполнить обратное преобразование для получения возвращаемого значения. Если вы компилируете код для SSE2, то это добавляет две инструкции в цепочку зависимостей и вычисления могут выполняться втрое дольше! В большинстве случаев такие задержки вполне допустимы, но во внутреннем цикле негативный эффект может быть весьма значительным.

Если вы компилируете для платформы x87, то преобразование в тип double ничего не стоит, а вот обратное преобразование затратно – настолько затратно, что некоторые оптимизирующие компиляторы выбрасывают это преобразование, а в результате можно получить КРАЙНЕ НЕОЖИДАННЫЕ результаты – например, CalcCircumference( r ) == CalcCircumference( r ) вернет false!

Сцена третья


Студент: Спасибо, учитель. Честно говоря, я не знаю, что такое SSE2 и x87, но я вижу, насколько элегантным становится код, когда типы согласованы. Это настоящая поэзия. Я буду использовать константу одинарной точности. Как вам вот это?

float CalcCircumference3(float d)
{
    return d * 3.14159265358979323846f;
}

Преподаватель: Да, превосходно! Символ «f» в конце константы все меняет. Если бы вы посмотрели на сгенерированный машинный код, вы бы поняли, что этот вариант намного компактнее и эффективнее. Однако у меня есть замечания к стилю. Не кажется ли вам, что этой загадочной константе не место внутри функции? Даже если это число Пи, значение которого вряд ли изменится, лучше присвоить константе имя и поместить в заголовочный файл.

Сцена четвертая


Студент: Спасибо. Вы объясняете все очень доходчиво. Я помещу строку кода ниже в общий файл заголовка и буду использовать ее в своей функции. Так нормально?

const float pi = 3.14159265358979323846f;

Преподаватель: Да, отлично! С помощью ключевого слова «const» вы указали, что переменная не должна и не может быть изменена, кроме того, ее теперь можно поместить в заголовочный файл. Но, боюсь, теперь нам придется углубиться в некоторые тонкости определения областей видимости в C++.

Объявив pi с ключевым словом const, вы получите в качестве бонуса эффект ключевого слова static. Для целочисленных типов это нормально, но если вы имеете дело с другим типом данных (число с плавающей точкой, массив, класс, структура), память под вашу переменную может быть выделена отдельно в каждой единице трансляции, которая включает в себя ваш заголовочный файл. В некоторых случаях у вас в итоге будет несколько десятков или даже сотен экземпляров переменной типа float и ваш исполняемый файл будет неоправданно большим.

Сцена пятая


Студент: Вы шутите? И что делать?

Преподаватель: Да, мы пока далеки от идеала. Вы можете повесить на объявление константы атрибут __declspec(selectany) или __attribute __(weak), для того чтобы VC++ и GCC, соответственно, поняли, что достаточно сохранить одну из многочисленных копий этой константы. Но поскольку мы с вами находимся в идеалистическом мире науки, я настаиваю на применении стандартных конструкций C++.

Сцена шестая


Студент: То есть примерно так? С помощью constexpr из C++11?

constexpr float pi = 3.14159265358979323846f;

Преподаватель: Да. Теперь ваш код идеален. Конечно, VS 2013 не сможет его откомпилировать, потому что не знает, что делать с constexpr. Но вы всегда можете воспользоваться набором инструментов Visual C++ Compiler Nov 2013 CTP либо последней версией GCC или Clang.

Студент: А #define можно использовать?

Преподаватель: Нет!

Студент: А, к черту все это! Лучше я стану бариста.

Сцена седьмая


Студент: Стоп, я кое-что припоминаю. Это же просто! Вот как будет выглядеть код:

mymath.h:
extern const float pi;
mymath.cpp:
extern const float pi = 3.14159265358979323846f;

Преподаватель: Точно, в большинстве случаев это будет верное решение. Но что если вы работаете над DLL, как внешние функции будут обращаться к mymath.h в вашей DLL? В таком случае вам придется обеспечить экспорт и импорт этого символа.

Проблема в том, что правила для целочисленных типов абсолютно другие. Целесообразно и рекомендуется добавить в заголовочный файл C++ следующее:

const int pi_i = 3;

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

О том, что значит «static» в «const», я узнал несколько лет назад, когда меня попросили выяснить, почему одна из наших ключевых библиотек DLL вдруг прибавила в весе 2 МБ. Оказывается, в заголовочном файле был массив констант, и мы получили тридцать копий этого массива в DLL. То есть иногда это все же имеет значение.

И да, я по-прежнему считаю, что #define — ужасный выбор в данном случае. Может быть, это еще не самое худшее решение, но мне оно совершенно не нравится. Однажды я столкнулся с ошибками компиляции, вызванными объявлением pi с помощью #define. Приятного мало, скажу я вам! Замусоривание пространства имен — вот главная причина, почему следует избегать #define, насколько это возможно.

Заключение


Не знаю точно, какой урок мы извлекли из всего этого. Суть проблемы, которая возникает, когда мы объявляем в заголовочных файлах константу типа float или double либо структуру или массив констант, ясна далеко не всем. В большинстве серьезных программ из-за этого возникают дубликаты статических констант, и иногда они неоправданно большого размера. Полагаю, constexpr может избавить нас от этой проблемы, но у меня нет достаточного опыта его использования, чтобы знать наверняка.

Я сталкивался с программами, которые были на сотни килобайт больше своего «реального» размера — и все из-за массива констант в заголовочном файле. Я также видел программу, в которой в конечном счете оказалось 50 копий объекта класса (плюс еще по 50 вызовов конструкторов и деструкторов), потому что этот объект класса был определен как тип const в заголовочном файле. Иначе говоря, тут есть над чем подумать.

Вы можете увидеть, как это происходит с GCC, загрузив тестовую программу отсюда. Соберите её с помощью команды make, а затем выполните команду objdump -d constfloat | grep flds, чтобы найти четыре инструкции чтения со смежных адресов в сегменте данных. Если вы хотите занять больше пространства, добавьте в header.h следующее:

const float sinTable[1024] = { 0.0, 0.1, };

В случае с GCC прирост составит 4 КБ на одну запись преобразования (исходный файл), то есть исполняемый файл вырастет на 20 КиБ, даже если к таблице ни разу никто не обращается.

Как обычно, операции над числами с плавающей точкой связаны со значительными трудностями, но в данном случае, как мне кажется, в этом виновата слишком медленная эволюция языка С++.

Что еще почитать по теме:


VC++: как избежать дублирования и как понять, что дублирования не избежать

У компилятора в VC++ 2013 Update 2 появился параметр /Gw, который помещает каждую глобальную переменную в отдельный контейнер COMDAT, позволяя компоновщику выявлять и избавляться от дубликатов. Иногда такой подход помогает избежать негативных последствий объявления констант и статических переменных в заголовочных файлах. В Chrome такие изменения помогли сэкономить около 600 КБ (подробности). Частично такой экономии удалось добиться (сюрприз!) путем удаления тысячи экземпляров twoPiDouble и piDouble (а также twoPiFloat и piFloat).

Однако в VC++ 2013 STL есть несколько объектов, объявленных как static или const в объявлении класса, которые /Gw не может удалить. Все эти объекты занимают по одному байту, но в итоге набегает свыше 45 килобайт. Я сообщил разработчикам об этой ошибке и получил ответ, что в VC++ 2015 она была исправлена.

По просьбе автора делимся ссылкой на оригинал здесь
ABBYY 104,22
Action information
Поделиться публикацией
Комментарии 141
  • +5
    Весьма интересно было почитать.
    Хотя если студент может написать вариант из «Сцена седьмая», то у него может хватить мозгов объяснить преподавателю что если не первый вариант (он может скомпилироваться не везде), то второй на 100% соответствует поставленной задаче. Ведь в задание нигде не говориться про эффективность написанной функции.
    Т.е. для обучения студентов С++ пример хороший, но вот для обучения студентов работы с заказчиком и проработкой ТЗ следует взять тот же пример и показать, что при такой постановке даже первый вариант можно считать готовым решением.
    • +12
      Все же не совсем понятно, чем плох вариант с #define.
      Замусоривается глобальное пространство имен — может быть, но аналогичным образом оно замусоривается и с const, везде, где включается этот хедер.
      • +1
        Никто не мешает сделать как локальный дефайн (Уникальный причем!) так и локальную static const переменную.
        Зачем мусорить?
        • +1
          Может, речь о таких ситуациях?

          #define Pi 3.14
          
          <...>
          
          void foo()
          {
              int Pi = 0;//oops
          }
          
          • +14
            Обычно у дефайнов и переменных разный стиль именования. Например, дефайн капсом с подчеркиваниями, а переменные строчными буквами. Поэтому они пересекаться не должны.
            Кроме того, ну выдаст компилер ошибку, переименовать переменную не так трудно ведь?
            Это же не сегфолт в рантайме, исправить легко.
            • +8
              ну выдаст компилер ошибку, переименовать переменную не так трудно ведь?

              При всем уважении, но «должны» это не про реальные проекты. Бывает всякое, я как-то боролся с проблемой, потому что некто определил макрос L. Было несколько переменных внутри структур с таким же названием, кроме того, — это лексема для указания «широких» строк. Размер проекта примерно 700мб исходного кода. Причем раньше это работало (много лет), но стало разваливаться при обновлении версии компилятора. По-другому включилась последовательность h-ников и все, приехали. Найти это при таких объемах было чрезвычайно сложно, во-первых потому, что имя короткое и оно плохо ищется. Во-вторых ошибка вовсе не указывала на проблемное место, а всплывала за много «километров» от него, с совершенно невразумительной диагностикой. Если бы у меня на тот момент уже не выработался паттерн: «видишь непонятную хрень — виноваты макросы», я бы потратил еще больше времени.
              Так что, при всем уважении, ваши рассуждении просто говорят о том, что вы никогда не имели дел с legacy такого объема.
              Это же не сегфолт в рантайме, исправить легко.

              Сегфолты, к слову, обычно проще исправить, чем такое.
              • 0
                Да, с легаси такого объема я не сталкивался.
                Возможно, в таких ситуациях все иначе. Я говорю про более мелкие проекты.
                • +6
                  > потому что некто определил макрос L

                  Напомнили. Столкнулись с таким же, только макросом было W. Первые умники работают в Lotus (IBM), писали Lotus C API. А вторые умники добавили такое в boost. А нам, дуракам, которым надо было подключать и первое и второе, оставалось выбрать, кого патчить :-]
                  • 0
                    имя короткое и оно плохо ищется

                    Вы должно быть забыли поставить запятую, т.к. из того, что имя короткое совсем не следует, что оно плохо ищется :) Например вот вам команда с регуляркой «навскидку», которую я бы на вашем месте использовал «grep -rnIE "\bL\b[^\"]*"» — скипает бинарные файлы и «широкие» строки, выводит только строки с их номерами в файле, где используется одинокая большая Эл.
                    • 0
                      Вы должно быть забыли поставить запятую, т.к. из того, что имя короткое совсем не следует, что оно плохо ищется :) Например вот вам команда с регуляркой «навскидку», которую я бы на вашем месте использовал «grep -rnIE "\bL\b[^\"]*"» — скипает бинарные файлы и «широкие» строки, выводит только строки с их номерами в файле, где используется одинокая большая Эл.

                      Допустим мы представим, что я не знаю про регулярки (только допустим). Допустим, что я не умею пользоваться grep (только допустим). Теперь допустим я решил попробовать поискать таким образом у себя и получил лог в полтора миллиона строк (и это я еще обрезал поиск только по h, c и cpp). «Плохо ищется» означало, я не получу обозримый лог поиска. Я получу портянку на пару недель изучения :) Во-вторых, если к твоей регулярке добавить еще поиск вхождения по «define», то становится гораздо проще. Но это ведь нужно знать, что там надо писать define, компилятор об это не говорит. Хорошо, допустим я знаю про это (я знал, выше писал, сработал паттерн), но я все равно не нашел подобным образом ничерта. Потому что искал по коду комплекса, где у меня развалилась сборка, а макрос этот злосчастный был во внешних зависимостях.
                      • 0
                        В данном случае это следует из того, что это имя много раз применялось. Но с тем же успехом этим именем могло быть и какое-нибудь «myVeeryLongVariableName». Я к чему говорю: мой ответ касался вашего замечания, что переменная плохо ищется потому, что у нее короткое имя — логично было предположить, что вы не знаете про grep, и/или регулярки.
                        • 0
                          Я к чему говорю: мой ответ касался вашего замечания, что переменная плохо ищется потому, что у нее короткое имя — логично было предположить, что вы не знаете про grep, и/или регулярки.

                          Но контекст-то нужно учитывать. Я сперва озвучил цифры не просто так. В контексте, который я описывал, именно короткое имя является причиной «плохого поиска» и именно поэтому, чтобы смочь составить регулярку, нужно знать достаточно о вариантах применения, дабы отразить это в выражении. Если варианты неизвестны, то поиск сильно затрудняется.
                    • 0
                      имя короткое и оно плохо ищется
                      Кстати, удивительно, что в IDE-программе, априори способной на синтаксический разбор кода, искать обычно можно лишь текст без возможности указания типа искомой сущности — например, имя переменной, имя функции или что-то ещё.
                  • +1
                    Этот код не должен скомпилироваться, те офлайн ошибка, да и код ошибки будет довольно понятным: ideone.com/c0svLX
                    • +4
                      Может, научить студентов определять корректные названия переменных, особенно глобальных?
                      • +1
                        С такой позицией можно дойти и до асбтракных фабрик\thread workers и прочих изысков которые совершенно не нужны для поставленной задачи.
                        В результате получаем либо переученного\замороченного студента либо теряем\пугаем студента.
                        • +4
                          А можно просто вспомнить про пространства имен.
                      • 0
                        Вариант с «#define» плох тем, что, если «задефайненная» константа используется, утрируя, в пятидесяти местах в коде — препроцессор понавставит ее напрямую в пятьдесят мест, и, я сомневаюсь, что позже, при оптимизации, компилятор будет искать одинаковые числа.
                        Когда же используется «const», компилятор видит, что это одна и та же переменная, и не станет ее дублировать 50 раз.
                        • +1
                          Не думаю, что даже при использовании в коде 50 раз одна float-переменная займет много места.
                          Вариант с констом займет еще больше, как описано в статье, если файл инклудится многократно.
                          • 0
                            Разумное возражение. Но хочу заметить, что с другой стороны, при lto-оптимизации все дубликаты наверняка будут удалены, что крайне сомнительно в случае с «#define».
                            • +4
                              Флоат — всего 4 байта, если компилятор разместит эту константу в секции кода, например, инструкция чтения вашей глобальной const из секции данных меньше объем не займет.
                          • 0
                            Там вообще не будет никакой переменной, зачем она?
                            • 0
                              Если посмотреть на это с чисто технической точки зрения, то использование #define для подстановки этих 50 float'ов выгоднее. Если код будет компилироваться для 32-х разрядной машины, то при использовании const во всех 50 местах будет подставлен указатель на константу, который будет занимать 4 байта, как и непосредственно сам float, т.е. разницы нет. Но если вы скомпилируете код под 64-х битную архитектуру, то указатели будут занимать не 4, а 8 байт, т.е. в 2 раза больше, чем непосредственно само значение типа float. Тогда выгоднее использовать #define.
                              • +1
                                Если посмотреть на это с чисто технической точки зрения, то использование #define для подстановки этих 50 float'ов выгоднее.
                                Это с какого такого перепугу?

                                Если код будет компилироваться для 32-х разрядной машины, то при использовании const во всех 50 местах будет подставлен указатель на константу, который будет занимать 4 байта, как и непосредственно сам float, т.е. разницы нет. Но если вы скомпилируете код под 64-х битную архитектуру, то указатели будут занимать не 4, а 8 байт, т.е. в 2 раза больше, чем непосредственно само значение типа float.
                                Это для какой-такой архитектуры, я извиняюсь? Нет, я верю, что вы можете придумать такую архитектуру, где всё будет так, как вы описали, но это уже какая-то Аристотельщина началась, когда тысячи лет люди спорят о том, сколько у мухи ног, но почему-то взять муху в руку и их посчитать никому в голову не приходит.

                                Я про это уже писал: практически на всех современных архитектурах у вас нет выбора — загружать ли константу из памяти через указатель или встроить её прямо внутрь команды. На RISC'ах её просто некуда встраивать — команды 32битные (иногда 16битные), а там, кроме константы ещё должен быть опкод, номер регистра, куда всё это грузить и прочее. В x86 таких ограничений нет, но и команд для загрузки float'ов из immediate'а тоже нет.

                                А вот что будет выгоднее — зависит от кучи обстоятельств. На x86 вариант с defineами проигрывает всегда — без вариантов. А вот на каком-нибудь ARM'е многое зависит от компилятора и от программы. Так как «большой» указатель в инструкцию может не влезть, то может получиться так, что при выносе константы в отдельный файл вам потребуется загружать указатель в какой-нибудь регистр отдельной командой. Такая странная конструкция может привести к тому, что будет выигрывать либо один вариант, либо другой в завимости от опций компиляции и структуры кода! Но уж никак не в зависимости от разрядности процессора — тут AArch32 от AArch64 почти не отличается.
                          • +25
                            Примеры плохие. Если каждый будет вытаскивать лольканые константы частных фукций в заголовочные файлы — будет бред полный.
                            написать const double\float PI = 3.1415...; внутри ф-ии и все!
                            Ведь задача стоит в написании функции. Что является очень локальной задачей. И если при этом программист ради ф-ии в пару строк добавляет в проект новые глобальные константы да еще и много раз (в поиске мнимого идеала) меняет заголовочные файлы в проекте… печаль иметь такого учителя.

                            Те, как задачка для ума — забавно! Но делать это с позиции грамотного рабочего подхода к выполнению реальных задач — неприемлимо и учит плохому.

                            PS: Конечно все выше сказанное мое сугубо личное ИМХО, и было бы интересно узнать другие мненния ))
                            • +8
                              Студент: А, к черту все это! Лучше я стану бариста.

                              «Бариста должен знать температуру воды, давление в машине, то, как она работает, сколько граммов кофе нужно на чашку и с каким усилием его утрамбовывать, сколько секунд вода проходит через него. Количество кофе на чашку может меняться в зависимости от влажности, давления и температуры воздуха на улице. От этого зависит скорость прохождения воды и в конечном счете качество напитка.»
                              В общем те же фабереже, только в профиль :-)
                              • 0
                                Так ему уже хватит знаний что бы без заморочек написать С-говнокод под ардуинку для определения оптимального режима по датчикам и не будет париться ))
                                • 0
                                  А потом выясняется, что именно сегодня ему достались зёрна с дерева, которое росло на чуть более солнечном склоне, чем остальные — и для них идеальные параметры чуть-чуть иные.
                                  • +1
                                    Тогда дописываем код под ардуинку, добавив в него величину освещённости. Так, чтобы не заморачиваться, можно сделать константой, новые партии кофе не так часто закупаем… Так, куда бы эту константу впихнуть?
                                  • 0
                                    Думается мне, что все знания мира о заваривании кофе несколько меньше стандарта Си++.
                                  • +18
                                    И-эх, ждал разных методов вычисления числа Пи, от использования тригонометрических функций до численных методов и рандома, а топик оказался об особенностях использования констант и разных компилляторов.
                                    • +9
                                      Возможно, пропустил где-нибудь выше замечание… Но разве при работе с числами с плавающей точкой в принципе допустимо использование сравнений вроде CalcCircumference( r ) == CalcCircumference( r ), где CalcCircumference(...) возвращает float?

                                      Вроде бы обычно используют сравнение с контролем точности до какого-то знака.
                                      • +2
                                        Скорее тут речь была о детерминированности результата. Вызывается 2 раза одна и та же ф-ия с одинаковыми (бинарно) входными параметрами.
                                        • +3
                                          Такое сравнение всё равно недопустимо, даже с правильной и красивой функцией.
                                        • 0
                                          Если можно, приведите код примера сравнения с контролем точности до какого-то знака на С++
                                          • –1
                                            bool compareFloat(float x, float y, int precision)
                                            {
                                            	float multiplier = pow(10,precision);
                                            	return static_cast<int>(x * multiplier) == static_cast<int>(y * multiplier)
                                            }
                                            
                                            • 0
                                              Чего-то мне кажется что вряд ли это ситуацию исправит. Проблемы с невозможность задать всякое число с плавающей точкой с помощью float (из-за привязки к степеням двойки) и с особенностями его аппаратного вычисления никуда не денутся, сколько не домножай его на другие числа.

                                              Хотя, если вам такое решение помогло, интересно было бы почитать каким образом.
                                              • +1
                                                Возводить 10 в степень и делать два умножения для сравнения двух чисел? Можно попрощаться с производительностью, которая у плавающей точки и так не очень, по сравнению с целыми числами. Лучше, как это принято, передавать некий эпсилон, с которым сравнивать разницу x и y:

                                                bool compareFloat(float x, float y, float epsilon)
                                                {
                                                    assert(epsilon > 0.0f);
                                                    return abs(x - y) < epsilon;
                                                }
                                                
                                                • +1
                                                  Авторский код можно переработать, убрав возведение в степень в шаблон, и сместив таким образом это дело на этап компиляции.

                                                  На этот же этап можно сместить проверку того, что программист не затребовал точности больше, чем есть у float.
                                                  • +1
                                                    Только этот метод работает только для чисел около 0. Для любых x и y, вот более правильный метод: realtimecollisiondetection.net/blog/?p=89
                                                    • –1
                                                      Там куча проблем, взять хотя бы тот факт, что автор садит в одну формулу абсолютную и относительные погрешности. Как это обосновать? У нас из математического определения алгоритма следует, например, что цикл нужно остановить, когда абсолютная разница станет меньше эпсилон. А автор бабахает туда еще и относительную погрешность, причем без математического обоснования. А если по относительной погрешности все сошлось, а по абсолютной — еще нет, то есть определение алгоритма не выполнено?

                                                      Также замечу, что все численные алгоритмы уже внутри своего построения обеспечивают «значения около нуля» (обезразмеривание задачи) и учитывают погрешности при вычислениях, так что нет необходимости в применении данных фокусов — нужно правильно строить алгоритм.
                                                      • 0
                                                        Там куча проблем, взять хотя бы тот факт, что автор садит в одну формулу абсолютную и относительные погрешности. Как это обосновать? У нас из математического определения алгоритма следует, например, что цикл нужно остановить, когда абсолютная разница станет меньше эпсилон. А автор бабахает туда еще и относительную погрешность, причем без математического обоснования. А если по относительной погрешности все сошлось, а по абсолютной — еще нет, то есть определение алгоритма не выполнено?
                                                        Там написано почему: потому что сравнения с абсолютной и относительной погрешностями взаимоисключительно не работают на величинах разных порядков.

                                                        Также замечу, что все численные алгоритмы уже внутри своего построения обеспечивают «значения около нуля» (обезразмеривание задачи) и учитывают погрешности при вычислениях, так что нет необходимости в применении данных фокусов — нужно правильно строить алгоритм.
                                                        Чушь какая-то.
                                                        • 0
                                                          Почему чушь? Вы хотя бы один численный метод реализовывали? Учебник по математическому моделированию открывали?

                                                          Сравнение величин разных порядков — одна из главных ошибок реализации численных методов. Вместо исправления реализации, автор прелагает математически необоснованный костыль.
                                                • +1
                                                  Например, здесь описывают эту проблему и советуют разные способы её решения. Я использовал сравнение, которое в статье советуют не использовать: if( Math.abs(a-b) < epsilon), но оно работало для меня.

                                                  А ещё FoxCanFly тут же ответил — какой-то хитрый способ… Пытаюсь вот понять.
                                                • 0
                                                  Очень просто — сравнивать нужно число знаков в двоичной системе счисления
                                                  1. Разбить число на мантиссу и экспоненту
                                                  2. Сравнить экспоненты, совали — продолжаем, не совпали — ответ готов — 0 знаков.
                                                  3. Сравнить знаки мантисс, не совпали — ответ готов, 0 знаков.
                                                  4. Сравнить цифры мантисс, начиная с младших разрядов, нумерация с 0. Номер первого несовпадения и есть ответ.


                                                  Потом ответ можно «привести» к варианту для десятичной системы, аккуратно воспользовавшись школьными теоремами о логарифмах и смене основания.
                                                  • 0
                                                    Так тоже не получится. Тут проблема глубже. Дело в том, что, помимо погрешностей операций над числами с плавающей точкой на одном процессоре, на разных процессорах могут результаты тоже разные получаться. И если сравнивать через оператор равенства числа, получение с разных компьютеров (например, для мультиплеера), то при одних и тех же операндах могут разные результаты выходить.

                                                    Кстати, то, что вы предлагаете, если я правильно понял, это обычное сравнение — просто зачем-то очень сложное, ещё и зависимое от способа запаковки числа с плавающей точкой.
                                                    • 0
                                                      Ваше замечание очень полезно. Стоит однако учесть, что IEEE 754-2008, рекомендует изготовителям компиляторов и процессоров реализовывать арифметику так, чтобы результаты были воспроизводимы хотя бы в пределах одного языка.

                                                      У меня не «обычное» сравнение — оно сообщает, начиная с какого двоичного знака числа начинают различаться. Оно не привязано к способу упаковки числа — в С есть стандартные функции (fprec) для изъятия мантиссы и экспоненты, для которых в дальнейшем возможен перевод в целый тип.
                                                      • +1
                                                        оно сообщает, начиная с какого двоичного знака числа начинают различаться


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

                                                        Оно не привязано к способу упаковки числа — в С есть стандартные функции


                                                        Ух ты, слышал про такую возможность, но никогда не пользовался. Спасибо.
                                                    • 0
                                                      А не будет ли проблем вблизи степеней двойки? Ведь у чисел 0.999999 и 1.000001 экспоненты разные. Так что на втором шаге вы получите «не равны совсем».
                                                      Лучше уж как-нибудь так:
                                                      int Diff(float a,float b){
                                                        int ia=*(int*)&a,ib=*(int*)&b;
                                                        return abs(ia-ib);
                                                      }
                                                      

                                                      Вернёт относительную погрешность, умноженную примерно на 2^24 (для разных пар коэффициенты будут разными, но различаются они не более, чем вдвое). Не будет работать в окрестности нуля (особенно, когда числа разных знаков).
                                                      • 0
                                                        Так это и для степеней десятки верно-
                                                        1.000 и 0.999 — все знаки «после запятой» разошлись.
                                                    • –2
                                                      Если мы сравниваем два значения, полученные, скажем так, одинаковым путем, то лучше всего сравнивать модуль разности чисел с погрешностью, определенной для данного типа в стандартной библиотеки:

                                                      bool areSame(float a, float b)
                                                      {
                                                      return  std::fabs(a - b) <= std::numeric_limits<float>::epsilon()
                                                      }
                                                      
                                                      • +2
                                                        Действительная погрешность представления действительных чисел может на порядок чисел a и b отличаться от std::numeric_limits::epsilon(). Поэтому эта константа довольно бесполезна, точность должна определяться предметной областью, точностью входных данных.
                                                        • +1
                                                          Да. Можно домножать на характерный порядок величин (std::max(a, b), например).
                                                  • 0
                                                    Трудно быть С++ программистом. Спасибо за статью. То, что компилятор не инлайнит константы с плавающей запятой и клонирует их, на самом деле, стало для меня сюрпризом.
                                                    • +2
                                                      А это потому, что вы, в своё время не обратили внимание на то, как в процессоре реализована работа с плавучкой. На большинстве процессоров целочисленную константу можно встроить как immediate внутрь инструкции, а float или double — нельзя. Соответственно самый оптимальный способ — это оставить константу в покое и загрузить её из памяти.
                                                      • 0
                                                        Просто csc тот же инлайнит все константы, которые встречает, заменяя на численное значение. Константа может быть любым числом или строкой. Я был уверен, что в плюсах поведение такое же, ибо они славятся именно миллиардом и одной оптимизацией на этапе компиляции всего, что только возможно.
                                                        • +1
                                                          Ну дык свойства байткода и CPU-кода несколько разные. То, что хорошо ложится на байткод может плохо ложиться на CPU и наоборот. Те же константы с плавучкой могут-таки быть, например, на ARMе встроены в инструкцию — но только в огромным количеством ограничений, бóльшая же часть всё равно грузится из памяти.
                                                          • 0
                                                            Ну может вы и правы.

                                                            Но для справки, такой код:

                                                                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
                                                            
                                                            • 0
                                                              Потому что IL по степени высокоуровневости примерно как тот же C (а местами и куда повыше). Там надо смотреть, что потом сгенерит JIT из этого байткода.
                                                              • 0
                                                                Ну видимо вы правы:
                                                                --- c:\users\alex\documents\visual studio 2015\Projects\ConsoleApplication26\ConsoleApplication26\Program.cs 
                                                                        return d * PI;
                                                                00000000  fld         dword ptr [esp+4] 
                                                                00000004  fmul        dword ptr ds:[02D135A0h] 
                                                                0000000a  ret         4 
                                                                
                                                    • +15
                                                      Заголовок меня смущает, все-таки. Неясно, что значит вычислить окружность.
                                                      Правильнее было бы вычислить «длину окружности», как в тексте статьи.
                                                      • +2
                                                        Я тоже был уверен, что в статье будет разгром некорректно поставленной задачи, а оно вот как обернулось :)
                                                        • 0
                                                          Еще «окружность круга» — это тавтология.
                                                          • 0
                                                            Окружность != круг, так что не тавтология.

                                                            Просто бессмысленное выражение.
                                                        • 0
                                                          А как дела обстоят в Objective-C с такого рода проблемами дублирования? Может кто-то ответить?
                                                          • +4
                                                            acos(-1) даст вам желаемое PI в нужном типе.
                                                            • +5
                                                              Вычислять арккосинус каждый раз когда надо использовать Пи?
                                                              Это… Хм. Даже не знаю как назвать.
                                                              • +1
                                                                Если это C++11 и арккосинус реализован и помечен как constexpr — почему бы и нет?
                                                                • +1
                                                                  Уточню:
                                                                  constexpr auto pi_value=acos(-1);
                                                                  

                                                                • 0
                                                                  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. Да, отзываю.

                                                                  Кстати, насчёт «пи» у интела много написано. Цитирую:
                                                                  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.
                                                                • +2
                                                                  FLDPI
                                                                  • 0
                                                                    Упс, а слона-то я в этой PDF'ке и не заметил. Да, самая разумная инструкция из всего, что обслуждалось.
                                                                    • 0
                                                                      Есть только одна проблема: на современных системах вычисления лучше проводить не с использованием x87 инструкций, а с использованием SSE инструкций. А единственный способ «перегнать» данные из ST(x) в XMMx — через память. Так что смысла в FLDPI, увы, нету никакого.

                                                                      P.S. ATAN нонче тоже считают через полиномы, а не через FATAN2…
                                                                • +26
                                                                  Говорят, windows 10 будет занимать на 4.5 Гб меньше предыдущей версии.
                                                                  • +13
                                                                    Это они убрали функцию ресайза окна из ядра.
                                                                    • 0
                                                                      После многолетних экспериментов определили везде пи через #define

                                                                      А вы думали, почему дистрибутив разрастался?
                                                                  • +1
                                                                    То есть на Си таких граблей не будет?
                                                                    • +1
                                                                      на Си можно дефайном, никто косо не посмотрит :)
                                                                    • 0
                                                                      Тема с дублированием констант напомнила о похожей проблеме в Java. Если вы объявляете строковую константу (static final String) в классе и ссылаетесь на неё из другого класса, при компиляции она копируется в другой класс полностью. Если константа большая, ваш код может существенно вырасти в размере. Так однажды исправление подобной проблемы (инициализацию констант выполнили чуть позже) уменьшило размер стандартной библиотеки на 1Мб (это с учётом зипования).

                                                                      • +8
                                                                        лучше присвоить константе имя и поместить в заголовочный файл.

                                                                        Имя присвоить — можно. Но в заголовочный файл — зачем? Это нужно только в случаях, если ставится задача обеспечить видимость константы в других частях программы. Но исходная задача, которая была поставлена — вычислить длину окружности. Мало ли, какие там константы используются?

                                                                        Если бы студента попросили вычислить функцию Бесселя первого рода — то в алгоритмах ее вычисления тоже используются константы. Это «квадратные» числа, и вряд ли они могут понадобиться еще где-то, кроме как в дебрях алгоритма вычисления функции. Их что — тоже экспортировать?

                                                                        Все эти пляски с бубном из-за константы — Overengineering в чистом виде. Конечно, учить сложным и профессиональным методам как-то нужно, но применение их в простых случаях — это стрельба из пушки по воробьям. Некоторые наивные программисты, когда их научат такому, и тратят час на написание программы вычисления длины окружности. Вместо того, чтобы просто решить задачу адекватными средствами исходя из двух критериев: 1) получить нужный результат; 2) получить его быстро.
                                                                        • +3
                                                                          Как насчет удаления констант в пользу статической константной функции, помеченной inline?
                                                                          struct globals
                                                                          {
                                                                            static inline float gimmePi() const
                                                                            {
                                                                                return 9000; //hove a nice debug, lol
                                                                            }
                                                                          };
                                                                          
                                                                          • +1
                                                                            Я думаю, тут лучше бы подошли Variable templates.
                                                                            • 0
                                                                              Это если в проекте доступна такая роскошь, как C++11.
                                                                              //у меня, кстати, там const лишний. Метод уже и так статический.
                                                                              • 0
                                                                                Да и структура возможно лишняя, достаточно пространства имен.
                                                                                • 0
                                                                                  Там может что-нибудь вычисляться / строиться на основании констант во время компиляции. Еще можно сделать из этой структуры шаблон с разными специализациями, избавившись в некоторых местах от условной компиляции по #ifdef
                                                                                • +2
                                                                                  На самом деле C++14, что ещё несколько усложняет дело.
                                                                            • +25
                                                                              Сцена вторая мне уже не понравилась! Нефиг свои константы выдумывать!

                                                                              Студент: Как вам такой вариант?
                                                                              #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);
                                                                              }
                                                                              
                                                                              • +2
                                                                                Согласен, только не float(M_PI), а определить и использовать M_PI_F.
                                                                                • +1
                                                                                  Не согласен, оптимизировать в первую очередь надо алгоритм, а компилятор справится со всякой мелочью, вроде этой.
                                                                                  Неужели вы думаете, что для него проблема привести сиё число ко float во время компиляции?
                                                                                  Да и в любом случае потеря не велика.
                                                                                • +1
                                                                                  Я думаю, что комментарий "// support weird compilers" нужно-таки перенести к "#define _USE_MATH_DEFINES", а остальное — просто убрать. Потому как M_PI — это всё-таки стандартная константа, а единственная платформа, которая человеческие стадарты не соблюдает и с которой всё-таки приходится общаться — это Windows.
                                                                                  • 0
                                                                                    Я знаю, что стандартная. Я действовал в системе координат того преподавателя, который ставит гипотетические условия «а вот не все компиляторы поддерживают, что вы будете делать?»

                                                                                    Естественно в реальной практике о #ifndef M_PI не может быть речи.
                                                                                    • 0
                                                                                      В C99 этой константы нет. Так что это какой‐то местечковый стандарт вроде POSIX. Просто настолько удобный, что константу скопировали все адекватные libc и компиляторы. Кстати, glibc на моей системе спрятала M_PI под 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 тысяч с учётом других языков (генераторы?). А ведь есть ещё другие константы с аналогичным эффектом. Так что может, и ещё как.
                                                                                      • +1
                                                                                        я тут наткнулся на ваш коммент, и решил посмотреть те результаты. Они меня сперва насмешили, потому что там похоже все сгенерено автоматически (ну по большей части).
                                                                                        Но я решил подумать еще и посмотрел github.com/search?utf8=%E2%9C%93&q=define+M_PI&type=Code&ref=searchresults
                                                                                        И тут я уже заплакал… сотни тысяч M_PI с разной точностью…
                                                                                        • 0
                                                                                          А вот это вообще десятилетие гуляет:
                                                                                          #define M_PI 3.14159263
                                                                                          и никого не волнует…
                                                                                          • 0
                                                                                            «в военное время число пи может достигать четырех» чего переживаете :D там им наверное, 3.14 бы хватило…
                                                                                            • 0
                                                                                              Проблема в том, что мало того, что никогда не знаешь, с какой точностью у тебя M_PI (поменял порядок инклудов — и агась), так еще и просто ошибочное может
                                                                                • +2
                                                                                  Все ждал, на каком шаге будет проверка диаметра на неотрицательность, но пост не об этом.
                                                                                  И похоже, в своих проектах нужно на constexpr перебираться.
                                                                                  • +7
                                                                                    const float sinTable[1024] = { 0.0, 0.1, };
                                                                                    «Таблица грехов»? :)
                                                                                    • +1
                                                                                      У компилятора в VC++ 2013 Update 2 появился параметр /Gw, который помещает каждую глобальную переменную в отдельный контейнер COMDAT, позволяя компоновщику выявлять и избавляться от дубликатов.
                                                                                      Если я правильно понял, это аналог связки флагов gcc -fdata-sections и ld --gc-sections, которые очень любят в embedded (обычно с gcc -ffunction-sections).

                                                                                      Тем, кто захочет почитать об этом флаге в MSDN: не читайте русскую версию, если хотите что-то понять.
                                                                                      компоновщик использует общей оптимизации программы для сравнения разделы СOMDAT через несколько объектных файлов объекта
                                                                                      Можно также использовать /OPT: Брандмауэр подключения к интернету и компоновщика /LTCG и слияния в исполняемом файле любые идентичные только для чтения глобальных данных через несколько объектных файлов объекта компилировали с параметром /Gw.
                                                                                      • +5
                                                                                        Тем, кто ставит автоперевод, да еще и на технические сайты, видимо не дает покоя слава знаменитых гуртовщиков мыши.

                                                                                        Особую эффективность команда «Послать на ...» приобретет при передачи
                                                                                        посланий через Е-почту и общение с вашими коллегами и друзьями в местной
                                                                                        сети-работе. Попробуйте мощь команды «Послать на ...», и вы быстро
                                                                                        убедитесь, что без нее трудно существовать под Окнами 95.

                                                                                        Пишите нам и помните, что Microsoft компания всегда думает о том, как
                                                                                        вас лучше сделать.

                                                                                      • +2
                                                                                        Все примеры начиная со «сцены четвёртой» — некорректны, поскольку нам дали задание написать ОДНУ ФУНКЦИЮ, а прав замусоривать глобальное пространство имён своими фантазиями — никто не давал. Если Пи будет нужно и другим функциям — мы вынесем его, как только получим задание их реализовать.

                                                                                        Вторая мысль в том, что мало кто программирует «на стандарте», обычно люди программируют всё-же под какой-то один или несколько компиляторов, а все они, так или иначе, имеют своё определение Пи — вот его и нужно использовать, а не плодить сущности. Вариант mapron — самый близкий к идеалу.
                                                                                        • +1
                                                                                          А чем плох дефайн?

                                                                                          И интересно, как правильно писать под микроконтроллеры — там эффективность очень важна…
                                                                                          • +1
                                                                                            А почему нельзя
                                                                                            const long PI = 31415926535;
                                                                                            const long PIFRAC = 10000000000;
                                                                                            float CalcCircumference1(float d)
                                                                                            {
                                                                                                return d * 1.0 * PI / PIFRAC;
                                                                                            }
                                                                                            

                                                                                            ? Так как это const'ы — они должны сколлапситься в момент трансляции.
                                                                                            • 0
                                                                                              Потому что PI очень хочется сделать доступным не только в этой единице трансляции.
                                                                                              UPD: Прошу прощения, не увидел про long
                                                                                            • +1
                                                                                              Как вариант:

                                                                                              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();
                                                                                              }
                                                                                              
                                                                                              • 0
                                                                                                Разве что const там не нужен.
                                                                                              • –1
                                                                                                Забавно, что никто так и не вспомнил, что длина окружности — это 2*pi*r.
                                                                                                • +1
                                                                                                  Пожалуйста, напишите на C++ функцию, которая получает диаметр круга как float и возвращает длину окружности как float
                                                                                                • +5
                                                                                                  Прошу прощения, но мне показалось, что вместо решения поставленной задачи бедный студент борется с особенностями ЯП и разных его реализаций (при всем уважении к С++)… ИМХО.
                                                                                                  А потом заказчик вместе с начальником отдела разработок ломают голову: почему программисты 3 месяца решают задачу, которая решается за 3 дня?
                                                                                                  Ответ: потому что вместо решения поставленной ПРИКЛАДНОЙ задачи они (программисты) думают, например, над тем, куда число pi засунуть, чтобы как в ВУЗе учили… (утрирую). Когда программу пишешь — нужно над решением поставленной задачи думать, а не с компилятором бороться и самовыражаться. Заказчику наплевать куда это pi засунете — ему продукт нужен, чтобы вовремя и работал. ИМХО, одним из минусов С++ в обучении программированию и выступает то, что вместо того, чтобы решать задачу, бедный неподготовленный студент борется с языком… Можно кидать в меня помидоры, но это мое мнение… А статья — показательная и интересная. Спасибо Автору!
                                                                                                  • +1
                                                                                                    В реальном проекте такой вызов написали бы максимально простым, а потом исправляли бы в случаи возникновения проблем. И такой подход, как на меня, правильный.
                                                                                                    • +3
                                                                                                      > Когда программу пишешь — нужно над решением поставленной задачи думать, а не с компилятором бороться и самовыражаться.

                                                                                                      А в итоге получается MMO, у которой интерфейс написан на флэше и жрёт почти все ресурсы проца, и в итоге даже на относительно мощных системах GPU загружен меньше чем наполовину, а FPS до 20 с трудом поднимается.
                                                                                                    • +1
                                                                                                      Число Пи здесь указано недостаточно точно, но дело в том, что целочисленные константы в заголовочных файлах не требуют выделения памяти в отличие от остальных констант

                                                                                                      Не требуют выделения памяти до тех пор, пока кто-то не возьмет адрес у такой константы:

                                                                                                      const int pi_i = 3;
                                                                                                      const int* n = &pi_i;
                                                                                                      
                                                                                                      или
                                                                                                      void foo(const int* n);
                                                                                                      foo(&pi_i);
                                                                                                      

                                                                                                      Компилятор может по возможности не выделять память под константу, а не обязан это делать всегда.
                                                                                                      • +1
                                                                                                        Я на 100% не уверен, но, кажется, в C++11 уже обязан. Это сделали ради классов: раньше описав такую константу в классе в .h её приходилось описывать ещё и в .cc файле даже если её адрес никогда явно не брался. Сейчас — в стандарте появилось понятие «Integral constant expression», которое компилятор обязан вычислять в compile-time.
                                                                                                        • +2
                                                                                                          Понятие integral constant expression в стандарте было с самого начала, и статический (явно или неявно) const int с соответствующим инициализатором под него тоже всегда подпадал. Но это все перпендикулярно тому, выделяется ли память под переменную. Т.е. если у вас написано что-то вот такое:
                                                                                                          const int n = 0;
                                                                                                          int a[n];
                                                                                                          

                                                                                                          То компилятор обязан это съесть, хотя размер массива обязан быть константой времени компиляции. Но при этом он имеет полное право выделить память под n как переменную.

                                                                                                          >> раньше описав такую константу в классе в .h её приходилось описывать ещё и в .cc файле даже если её адрес никогда явно не брался.

                                                                                                          Для констант, у которых инициализатором является integral constant expression, их всегда можно было определять в теле класса вместе с инициализатором, а определять их снаружи требовалось только тогда, когда берется их адрес.
                                                                                                          • –2
                                                                                                            Размер массива уже лет 16 (с C99) не обязан быть константой времени компиляции. Убедитесь сами.
                                                                                                            • +4
                                                                                                              Тут речь, вообще-то, о плюсах, в которых нет VLA. То, что gcc компилирует ваш код — это следствие того, что он поддерживает сишные VLA в плюсах как языковое расширение, но по стандарту это ошибка. Кстати, со времен C99 появился еще C11, в котором эта фича сделана conditionally supported, т.е. не все компиляторы обязаны ее поддерживать.

                                                                                                              Впрочем, в моем оригинальном примере подразумевалось, что этот код не в функции. В этом случае константа таки требуется даже и в С99, VLA там допустим только для локальных переменных.

                                                                                                              • –1
                                                                                                                Спасибо. Покажите пожалуйста параграф/раздел стандарта C++, в котором явно написано, что относительно таких массивов нет обратной совместимости со стандартом C.
                                                                                                                • +1
                                                                                                                  Такого параграфа нет, поскольку C++ отпочковался от C до появления стандарта C99 (в 98-м году). Поэтому в соответствующем разделе стандарта (C.1[diff.iso]) описывается разница с C89.

                                                                                                                  Впрочем, это не суть важно — стандарт плюсов вообще практически ничего не определяет в терминах «а вот это как в 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.

                                                                                                                  • 0
                                                                                                                    Спасибо. Очень жаль, что я ошибся насчет массивов и вызвал эту дискуссию.
                                                                                                                    • 0
                                                                                                                      Очень жаль, что вас за это минусуют зачем-то. Это, мягко говоря, неочевидная вещь, про которую в книгах обычно не прочитаешь, компиляторы могут расширять стандарт (и более того, если они и так реализуют C99, то им легче здесь допустить расширение, чтобы не держать разную логику для C и C++), а сам стандарт C++14 — книга на 1300 страниц, которую читают, в основном, авторы компиляторов и библиотек в Boost, ну и language lawyers :)
                                                                                                                      • 0
                                                                                                                        Вообще-то это вещь, про которую пишет даже 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.
                                                                                                                        • +1
                                                                                                                          Простите, но мое чувство справедливости не дает проигнорировать этот момент.
                                                                                                                          Тут речь, вообще-то, о плюсах, в которых нет 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.

                                                                                                                          Короче говоря неконстанту в размере массива писать теперь официально можно и это не ошибка.
                                                                                                                          • 0
                                                                                                                            Это черновик четырнадцатого стандарта?
                                                                                                                            • +3
                                                                                                                              В каком именно новом стандарте? Я цитировал C++14, это актуальная на данный момент версия стандарта. То, что вы привели — это из текущего черновика C++17?
                                                                                                                              • 0
                                                                                                                                Я цитировал черновик С++14, но видимо немного более старый, чем сейчас есть на сайте.
                                                                                                                                Но, видимо, изменения откатили обратно (и это прошло мимо меня). Вот, например, патч для gcc по поводу этого.
                                                                                                                                Признаю свою вину, что не сверился с последним черновиком.
                                                                                                                                Вы были правы.
                                                                                                                                • +4
                                                                                                                                  Понятно.

                                                                                                                                  Кому интересно — там вообще довольно забавная история. Иметь VLA в какой-то форме (т.е. по сути — типобезопасный и стандартизированный alloca) всем хочется, но представления о том, как он должен выглядеть, были разные. Одна группа проталкивала уже существующий в C99 вариант, с основной мотивацией в виде совместимости с C, но он изрядно усложняет язык, поэтому их выпилили.

                                                                                                                                  С другой стороны был вариант с std::dynarray, который определен как библиотечный класс, но с таким интерфейсом, чтобы компилятор по факту мог сделать хитрооптимизированную реализацию с выделением на стеке (а мог и не делать — т.е. это QoI, а не требование). Его тоже сначала сгоряча добавили в C++14 (самое забавное, что в стандарте некоторое время были обе эти вещи), а потом выпилили на более поздних этапах, но не окончательно, а с выносом в отдельный Arrays TS. Так что в C++17 он, видимо, будет именно в таком виде.

                                                                                                          • 0
                                                                                                            Где почитать подробнее про то, какие константы требуют выделения памяти, а какие нет?
                                                                                                            • +1
                                                                                                              Что-то мне подсказывает, что если в наборе команд исполнителя (процессора или виртуальной машины) есть команды загрузки аргумента команды в регистр, то константа попадает сразу «в код». Если нет — то в сегмент данных и оттуда достается по оффсету/индексу. Ну то есть на ваш вопрос нет однозначного ответа, если вы не указываете на чем работаете.
                                                                                                              • 0
                                                                                                                Какой-нибудь интересный компилятор может с удовольствием загрузить значение при помощи инструкций типа LDI (если это AVR), ну или варианта MOV с константой, если это x86.
                                                                                                              • 0
                                                                                                                Если говорить о стандарте, то это регламентируется пунктом 3.2 One definition rule. [basic.def.odr]

                                                                                                                А именно, там определяется, какие переменные, функции и т.д. называются 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 ограничение для чисел с плавающей точкой было снято, и теперь литералы с плавающей точкой считаются константными выражениями (хотя стандарт оговаривает, что вычисления на этапе компиляции могут давать другой результат, чем в рантайме), и значит не обязаны быть определены в каждой единице трансляции.
                                                                                                                • 0
                                                                                                                  Тут надо отметить, что даже если переменная ODR-used, это не налагает на компилятор обязательство выделять память. В конечном счете, если подобное выделение не наблюдаемо в самой программе, то включается правило «as if».
                                                                                                              • –2
                                                                                                                А ещё можно сделать вот так:

                                                                                                                #include <boost/math/constants/constants.hpp>
                                                                                                                
                                                                                                                float CalcCircumference1(float r)
                                                                                                                {
                                                                                                                   using namespace boost::math::float_constants;
                                                                                                                   return 2.0f * pi * r;
                                                                                                                }
                                                                                                                

                                                                                                                По моему опыту, это зачастую наиболее простое и переносимое решение данной проблемы.
                                                                                                                • +4
                                                                                                                  Вы смеётесь? Простое? Переносимое?
                                                                                                                  Потом вас попросят этот «хеллоциркле» перенести на какой-нибудь калькулятор — вы с бустом будете танцы устраивать?
                                                                                                                  Статья не о том, что есть «единственно правильный вариант».
                                                                                                                  Статья о том, что даже в простейших с виду вещах могут быть такие вещи, о которых новичок не предполагает, а гуру набил себе шишки.
                                                                                                                • +4
                                                                                                                  Это все происходит потому, что преподаватель при постановке задачи озвучил меньше требований, чем предполагал. На эту тему даже был анекдот про прапрщика и воду с газом или без газа. Конечно, возможен вариант, что некоторые требования подразумевались актуальными по умолчанию, а мы тут просто видим вырваную из контекста беседу.

                                                                                                                  Надо было сразу сказать: хочу чтобы оно компилилось на всем что можно, и занимало как можно меньше памяти. Возможно даже надо указать конкретные платформы, наборы библиотек, компиляторы и ключи запуска. Или может стоит оставить ключи запуска на усмотрение студента. Потому что необязательно требования такие всегда. Например, на спектруме применял следующий прием: угол два пи это 256 «градусов», и вычисляем синус по табличке. Работало очень быстро, но в памяти надо было держать эту таблицу.

                                                                                                                  Опять же, если изначально стоит задача написать приложение под конкретную платформу, и там уже есть нужные либы — то зачем повторять их функционал? А если все равно повторяешь их функционал — зачем тогда вообще использовать либы? Могут быть случаи где этому есть разумное обьяснение, а могут быть случаи, где нет.
                                                                                                                  • –1
                                                                                                                    Можно, конечно, на курсах заниматься такой ерундой и так и не дойти до реальных программ, погрязнув в поисках идеального определения константы. А можно ограничиться первым или вторым вариантом и продожить знакомиться с чем то более интересным и полезным.
                                                                                                                    • +1
                                                                                                                      Читал на одном дыхании! Очень интересная статья, спасибо
                                                                                                                      • –6
                                                                                                                        Если бы ко мне так придерались на собеседовании, то были бы посланы на____. Я пишу, как хочу, преобразую типы где надо и не надо, вставляю непонятные константы посредине кода, а над совместимостью компиляторов пусть думают создатели компиляторов.
                                                                                                                        • –1
                                                                                                                          Интересно почему ПИ округлили до 3. Надо округлять до 4, ведь это степень двойки! XD
                                                                                                                          • –2
                                                                                                                            const int pi_i = 3;
                                                                                                                            Неправильно ейто.

                                                                                                                            #ifdef STATE_INDIANA
                                                                                                                            const int pi_i = 4;
                                                                                                                            #else
                                                                                                                            const int pi_i = 3;
                                                                                                                            #endif
                                                                                                                            
                                                                                                                            • 0
                                                                                                                              Задача решена первым же вариантом студента.
                                                                                                                              В формулировке нету ни слова про версии компилятора, эффективность и прочие дополнительные условия, появляющиеся по ходу решения.
                                                                                                                              «Пожалуйста, напишите на C++ функцию, которая получает диаметр круга как float и возвращает длину окружности как float»

                                                                                                                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                                                              Самое читаемое