LINUX.ORG.RU

ANSI C++: вычисление дробной степени (SFINAE)

 , , , ,


0

2

Всем привет!

Подскажите пожалуйста, как в классическом C++ можно с помощью шаблонов, принципа SFINAE посчитать результат возведения положительного дробного числа в положительную дробную степень во время компиляции.

Общая идея:

a^x = exp(x * ln(a))

В свою очередь ln(a) можно разложить в ряд Тейлора:

ln(a + 1) = [ a / (1!) ] - [ a^2 / (2!) ] + [ a^3 / (3!) ] - ...

Экспонента тоже раскладывается в ряд Тейлора:

exp(p) = 1 + [ p / (1!) ] + [ p^2 / (2!) ] + [ p^3 / (3!) ] + ...

Вроде бы дело не хитрое, надо рекурсивно вызывать шаблоны и все получится. Но в C++ нельзя получить дробные аргументы шаблона.

Собственно вопрос: подскажите как можно обойти это ограничение и таки посчитать a^x в compiletime.

★★★

Последнее исправление: zx_gamer (всего исправлений: 1)

А define в плюсах разве не работает? Вроде на дефайнах всё можно (на плюсах давно не писал и никогда не писал на них хорошо). Рекурсивный вызов шаблонов звучит для меня как что-то неприличное (рекурсия вообще зло и любая рекурсия может быть заменена на цикл, а ты её ещё к шаблонам хочешь). Вообще во времена царя гороха это через define и boost/preprocessor/repeat.hpp делалось а как там нынче у хипстеров модно я не знаю. Ну и оптимизации в компиляторе если могут то в compile time всё посчитают. Исключение если у тебя какой-то embedded с тупым компилятором который не умеет в оптимизации от слова совсем ну или если ты код на асме посмотрел и он действительно не оптимизировал тебе твой код из-за каких-то косяков компилятора.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)
  1. Есть constexpr

  2. Дробное число можно задать двумя интами

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

AntonI ★★★★★
()
Ответ на: комментарий от peregrine

Циклов в шаблонах нет, единственное, что можно сделать тьюринг-полного — так это рекурсия.

А что касается препроцессора: как вы хотите это реализовать с его помощью?

zx_gamer ★★★
() автор топика
Ответ на: комментарий от AntonI

2. Идея логичная. Как превратить дробное число в два целых понятно: (x / 1) и (x % 1), но как превратить два целых в дробное но без циклов? Опять написать SFINAE шаблон, который это делает?

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

https://stackoverflow.com/questions/1066099/can-i-compute-pow10-x-at-compile-...

Чёт типа такого не? Я про ответ с 11 плюсами в топике. Первое что нагуглилось.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Расскажите, пожалуйста, подробнее. Ряд Тейлора это первое что пришло в голову.

Не моя область специализации. Когда-то нам что-то про это немного рассказывали, но скорее всего для каждой базовой функции есть своя специфика.

Но если хочется просто предложить решение лучше, чем ряд Тейлора, то наиболее логично использовать приближение многочленами на заданном интервале, например, многочленами Чебышева, (семейств многочленов, используемых для приближений, довольно много), а операции с многочленами делать с помощью БПФ.

soomrack ★★★★★
()
Ответ на: комментарий от zx_gamer

Не, так не выйдет дробное в два целых превратить, проще всего юзать fixed point.

Ещё раз, чем Вам не нравится автомат хидер с нужнымии константами сгенерить? В данном случае это самый простой и прямой путь.

AntonI ★★★★★
()
Ответ на: комментарий от soomrack

Можно поковырять сырцы стандартной мат.либы конечно, но в этом можно утонуть. Отдельный большой мир.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

Не, так не выйдет дробное в два целых превратить, проще всего юзать fixed point.

Да, действительно, что-то забыл.

Ещё раз, чем Вам не нравится автомат хидер с нужнымии константами сгенерить? В данном случае это самый простой и прямой путь.

Потому что цель написать библиотеку compile-time математики для C++

zx_gamer ★★★
() автор топика
Ответ на: комментарий от AntonI

Оптимизация сейчас не первостепенна, сейчас главное в принципе заставить компилятор это делать, а как оптимизировать — дело десятое. Самое плохое, что произойдет — компилятор будет дольше транслировать программу и потреблять при этом больше оперативки.

zx_gamer ★★★
() автор топика
Последнее исправление: zx_gamer (всего исправлений: 1)
Ответ на: комментарий от peregrine

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

zx_gamer ★★★
() автор топика
Ответ на: комментарий от AntonI

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

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

главное в принципе заставить компилятор это делать

Главное понять зачем заставлять компилятор это делать. Я занимаюсь численным моделированием почти 30 лет и иногда делал очень странные и извращенные вещи, но даже моё испорченное воображение тут пасует… :-(

AntonI ★★★★★
()
Ответ на: комментарий от zx_gamer

А чем плох вариант проинициализировать нужные переменные в рантайм один раз?

И если для себя, то почему нет constexpr?

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

Ну в принципе, как курсовик по методам вычислений с упором на программирование, для одной функции… почему бы и нет? И язык проработать можно и в математике подразобраться…

Но задание может быть сформулировано просто – у кого для 1000 испытаний на заданном промежутке программа посчитает быстрее, с максимальной погрешностью ниже такого порога, тому пятерка по практике…

Но это скорее, как задача со звездочкой, для тех кто хочет автомат заранее.

soomrack ★★★★★
()
Ответ на: комментарий от AntonI

Идея навеяна множеством хаков на Си++. Хочется выжать из компилятора «невозможное» — сделать то, что авторы языка и не предполагали возможным.

Собственно SFINAE это и есть один из немногих «невозможных» трюков.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от AntonI

А чем плох вариант проинициализировать нужные переменные в рантайм один раз?

а) Это не даст нормально работать оптимизатору. Он не сможет предпосчитать значения, которые мог бы.

б) Это будет использование переменных, вместо значений.

И если для себя, то почему нет constexpr?

Потому что в нормальном C++ нет constexpr.

Нормальным C++ я считаю ANSI C++, который описан в «The C++ Programming language 2nd edition» Бьерна Страуструпа.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

SFINAE уже ненужен, есть концепты, они гораздо проще и понятнее.

Конечно джаст фор фан че токо не сделаешь, но если хочется сделать что то сложное и действительно полезное - обращайтесь:-)

AntonI ★★★★★
()
Ответ на: комментарий от zx_gamer

Нормальным C++ я считаю ANSI C++, который описан в «The C++ Programming language 2nd edition» Бьерна Страуструпа.

Он регулярно книгу переиздает.

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

soomrack ★★★★★
()
Ответ на: комментарий от soomrack

В качестве такого курсовика я бы предложил сделать систему автодифференцирования на шаблонах - это бы хотя бы имело смысл:-)

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Нормальным C++ я считаю ANSI C++, который описан в «The C++ Programming language 2nd edition» Бьерна Страуструпа.

Страдания ради страданий. Кстати, вся эта базовая математика в 26 стандарте constexpr, но по факту уже сейчас такая, можно спокойно дергать в компайлтайме всякие exp() с друзьями и передавать результат в параметы шаблона, например. Коты в таких случаях яйца лижут

kvpfs_2
()
Ответ на: комментарий от soomrack

Так называемые «стандарты» C++ после ANSI C++ это ярчайший пример дебилизма.

а) STL. Кто этот кусок говна, а иначе его назвать невозможно, затолкал в язык? Между прочим, его автор А. Степанов заявлял, что ООП — это методологическая ошибка. Именно поэтому там нет интерфейсов, нет нормальных методов, только «универсальные» функции, которым надо передавать 2 итератора. И это лишь одна малюсенькая часть почему STL ужасен.

б) «Магия». В язык не добавляют новых полезных конструкций. Вместо этого пишут функция «std::что-то_там» должна сделать то-то (при том, что в языке нет средств для того, чтобы сделать это). STL, которая никуда не годится (см. п. «а»), становится неотделима от языка. То есть вместо реально нужных возможностей языка, мы имеем какую-то уродливую «библиотеку», работающую на чистой магии.

в) Поломка обратной совместимости. Твари засидающие в ISO считают, что если сказать, что throw в объявлении функции больше не работает — это нормально. Выпилить какой-нибудь класс из STL (например, std::auto_ptr) — нормально. Сказать что цикл «without side effects» — UB — нормально. Причем это будет делаться с таким лицемерием: «ну в более старых стандартах не была описана модель исполнения, так что это всегда UB было».

г) Добавление ненужного хлама. Вот зачем =delete? Конструктор по умолчанию, конструктор копирования, operator= и т.п. нельзя просто private сделать, как всегда и делали? И подобной фигни очень много.

д) Добавление бесполезных (и очень сложных!) спорных решений. Модули, корутины и т.п. Модули вообще ничего не дают (только не позволяют использовать макросы), при том, что их не поддерживает ни одна известная библиотека, а значит это бесполезное усложнение компилятора. Корутины (правда другие, stackfull) можно реализовать библиотечно, например Boost.Coroutine, stackless невозможно отлаживать. И т.д. и т.п.

Поэтому С++ новее ANSI C++ я считаю ложкой меда в бочке дегтя.

zx_gamer ★★★
() автор топика
Последнее исправление: zx_gamer (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Может быть ты просто не понимаешь зачем это нужно и где это стоит применять?

Когда-то я тоже не любил все эти новшества и для меня С++ был языком «С с классами» (ну с плюшками типа перегрузка операций и пр.), а потом я проникся. Правда, на это повлияло программирование на других языках, в т.ч. на perl…

Посмотри серию лекций «Back to Basics», многие очень хорошие: https://www.youtube.com/watch?v=QoaVRQvA6hI

soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от soomrack

А я попрограммировал на Java и понял, что никто из этих сумасшедших стандартизаторов не представляет как должен выглядеть нормальный ООП язык.

Но когда им это говоришь, в ответ слышишь, что C++ это вообще-то не ООП язык, а «мултипарадигменный». Видете ли, на шаблонах можно в функциональном стиле писать, а без классов, как на Си, в процедурном.

Поэтому наслаждайтесь этим:

std::vector<int>::iterator t;
if ((t = std::find(my_vector.begin(), my_vector.end()) != my_vector.end()) { ... }

И подобными «мультипарадигменными» прелестями!

zx_gamer ★★★
() автор топика
Последнее исправление: zx_gamer (всего исправлений: 1)
Ответ на: комментарий от AntonI

цель написать библиотеку compile-time математики для C++

О_О позвольте полюбопытствовать напуркуа?!

например чтобы обогревать воздух :-)

https://github.com/ArashPartow/exprtk . Красиво сделана, удобно.. Но блин, header-only и оно столько времени компилируется что можно не только кофе попить, но и рабочий день прям сразу закончить. Утром узнать результат :-) И порой требует спец-ключей gcc

и это просто калькулятор..

MKuznetsov ★★★★★
()
Последнее исправление: MKuznetsov (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

Конечно как произведение искусства восхищает, до чего у некоторых сильны дух и креативность!

Как практика меня такое ужасно расстраивает, сколько труда и таланта пущено коту под хвост…:-(

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

К чему это высказывание? Поверьте, на C и C++ я давно программирую.

И когда вместо (например) такого API:

if (t = my_vector.find(value))

ISO предлагает это:

if ((t = std::find(my_vector.begin(), my_vector.end(), value) != my_vector.end())

у меня большие вопросы к их компетенции.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Верю что давно. Но тут вопрос не в сроках а в том что программировать и как. Хотя С++ и ужасный ЯП, он к сожалению для некоторых задач безальтернативен. Например для HPC. Тем не менее логика в интерфейсе find есть, а Вам никто не запрещает обмазать find своим синтаксическим сахаром.

Да, STL далека от идеала. Но, как уже отмечали, в новых стандартах иногда завозят весьма полезные вещи.

Меня же, как перфекциониста, удручает что талантливые люди тратят время на всякую бессмысленную фигню, вроде той которую Вы делать собрались. Причём чем люди квалифицированнее и талантливей, тем моё удручение больше.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 2)
Ответ на: комментарий от zx_gamer

Потому что цель написать библиотеку compile-time математики для C++

Тогда стоит и число pi в компайл-тайме вычислять. Зачем все эти константы, прочь ограничения :)

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

Вы предлагаете что-то вроде таблицы Брадиса сделать?

#define SIN_GRAD_0_0000 0.000000000
#define SIN_GRAD_0_0001 0.000001745
...

В теории можно, но это будет очень большой файл.

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

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

А что тут плохо? Не всегда же нужен поиск по всему диапазону.

if (auto it = std::find(my_vec.begin(), my_vec.end(), value); 
    it != my_vec.end()) {
   ...
}

Ну или через ranges сделать:

if (std::ranges::find(my_vec, value) != my_vec.end()) {
   std::cout << "exists!";
}
soomrack ★★★★★
()
Последнее исправление: soomrack (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Допустим, есть vector<int> и vector<float>.

В первом случае компилятор сгенерит два ПОЛНЫХ комплекта всех методов класса для каждого типа вектора.

Во втором будет всего две функции find - по одной для каждого типа вектора.

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

Вроде как-то так.

andreyu ★★★★★
()
Ответ на: комментарий от anonymous

Например контейнеры наследуются от IContainer. Метод find имеет прототип Iterator < T > find(const T&, Iterator < T > begin = get_begin_iter(), Iterator < T > end = get_end_iter()). Итераторы имеют operator bool (сравнение с итератором на элемент после последнего). Все.

zx_gamer ★★★
() автор топика