На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> long double 80 бит (Windows)
    Всем привет!

    Хочу использовать 80-битный long double (Windows x86 и x64). И вообще чтобы все операции производились с использованием FPU с точностью 80 бит (ибо SSE не работает с 80 битами).
    Объявляю переменные как long double, задаю все возможные ключи, которые нашёл:
    g++ -mpc80 -mlong-double-80 -m80387 -s -O2 "test.cpp" -o "test.exe"
    Всё равно смотрю отладчик – FPU настроен на точность 64 бита :facepalm:

    Что не хватает?

    p.s. Можно, конечно, влепить в начало asm-вставку с изменением настроек точности на 80 бит, но неужели это единственный способ? Для чего тогда -mpc80 сделали?
      Наверняка окажется, что 80-битная точность не ГОСТирована теми-то и теми-то, а потому использование оной - исключительно желание того или иного компилятора. :scratch:
        Э-э-э, ну 64 бита мантиссы – это вообще-то и есть 80-битный формат.
          Цитата Jin X @
          Что не хватает?

          Все хватает вроде бы, ну и славян дело говорит .

          https://en.wikipedia.org/wiki/Long_double
          Сообщение отредактировано: settler -
            Qraizer, причём тут мантисса?
            Про отладчик я ошибся (не знаю, что за глюк я поймал вчера), FPU настроен на 80 бит даже без опций.
            Но! Расчёты "внутри" (чтение/запись в локальные переменные) происходят с 64 битами.
            Смотрим:
            ExpandedWrap disabled
              // t.cpp
              #include <cmath>
              int x;
               
              int main()
              {
                x = round(M_PI + x);
                return 0;
              }
            g++ t.cpp -mlong-double-80 -mpc80 -m80387 -masm=intel -S -o t.s:
            ExpandedWrap disabled
              ...
                      mov     eax, DWORD PTR _x
                      mov     DWORD PTR [esp+20], eax
                      fild    DWORD PTR [esp+20]
                      fld     QWORD PTR LC0
                      faddp   st(1), st
                      fstp    QWORD PTR [esp]
                      call    _round
              ...
              LC0:
                      .long   1413754136
                      .long   1074340347
            Тут даже M_PI записан в 64-х битах! И всё чтение/запись идёт в 64-х битах.
            И никакие опции не помогают!

            Добавлено
            Вот, скажем, я не могу сделать так, чтобы корень вычислялся из 80-битного выражения, там идёт fstp qword и тут же fld qword с того же места (нафига?), а в x64 работа идёт через SSE2 (sqrtsd).
            ExpandedWrap disabled
              points += sqrt((radius2 - (uint64_t)xi*xi));
            Тут points и raduis2 - это uint64_t.
              А это тогда что?
              Цитата Jin X @
              Всё равно смотрю отладчик – FPU настроен на точность 64 бита

              Точность бывает 24, 53 или 64 бита. Какие форматы использует компилятор для репрезентации long double, к этому не имеет отношения.
                Цитата Qraizer @
                Точность бывает 24, 53 или 64 бита. Какие форматы использует компилятор для репрезентации long double, к этому не имеет отношения.
                Хорошо, пусть мантисса будет точностью, а всё вместе – размерностью или форматом. Хотя тут по-разному можно рассудить. Экспонента тоже указывает точность: 1E-1000 или 1E+1000 от мантиссы не зависит, а к точности (чисто математически) это тоже относится.
                Но если брать маны Интела, то он называет мантиссу "precision".
                Но тот же GCC пишет:
                Цитата
                -mpc80 Set 80387 floating-point precision to 80-bit.

                Но смысл, который я доношу вроде понятен же – это главное.
                К тому же, я написал ниже, что всё же формат FPU выставляется в 80 бит, но внутренние расчёты идут в 64-х битах.
                Как это исправить, как заставить GCC считать ВСЁ в 80 битах?
                  Всё он считать в 80 битах не будет. Только то, что имеет тип long double. У тебя в примерах нигде нет long double.
                  ExpandedWrap disabled
                    #include <cmath>
                     
                    long double x;
                     
                    int main()
                    {
                      x = round(M_PI + x);
                      return 0;
                    }
                  ExpandedWrap disabled
                            fld     TBYTE PTR _x
                            fld     TBYTE PTR LC0
                            faddp   st(1), st
                            fstp    QWORD PTR [esp+8]
                            fld     QWORD PTR [esp+8]
                            fstp    QWORD PTR [esp]
                            call    _round
                            fstp    TBYTE PTR _x


                  Добавлено
                  round() нигде не принимает long double, т.к. для её работы – округления к целому – и double с головой достаточно.
                    Цитата Qraizer @
                    round() нигде не принимает long double, т.к. для её работы – округления к целому – и double с головой достаточно.
                    Не соглашусь, не достаточно. И аналогичная ситуация с sqrt.
                    ExpandedWrap disabled
                      #include <iostream>
                      #include <cmath>
                       
                      int main()
                      {
                        long long x = 999999999999999999;  // чтобы показать, что число не превышает 64 бита :)
                        long double n = x;
                        x = round(n);
                        std::cout << x << std::endl;
                        x = trunc(n);
                        std::cout << x << std::endl;
                        x = sqrt(n);
                        std::cout << x << std::endl;
                        return 0;
                      }
                    Выводит:
                    ExpandedWrap disabled
                      1000000000000000000
                      1000000000000000000
                      1000000000

                    А теперь убери 4 девятки и будет всё ок.

                    Добавлено
                    В Delphi такой проблемы нет...
                      Решение найдено!
                      truncl, roundl, sqrtl :D
                        Цитата Jin X @
                        Решение найдено!
                        truncl, roundl, sqrtl

                        А не в дебри ли Си-шной либы тебя понесло? :) lround, llround, ... есличо.

                        ExpandedWrap disabled
                          long long std::llround( long double arg );
                          long double std::sqrt( long double arg );
                          long double std::trunc( long double arg );
                          По Стандарту целые значения могут быть преобразованы к вещественным, однако взаимоотношения разных типов равноправны. В <cmath> до С++11 лежат сигнатуры, только принимающие float, double и long double, т.е. они перегружены. Поэтому при использовании функций с целым аргументом компилятор неминуемо даст ошибку разрешения перегрузки. В C++11 были добавлен синопсис функций, принимающий интегральные значения, который должны кастоваться в double.
                          Jin X, используй long double. Компилятор не будет вместо тебя заменять double на long double.
                          Сообщение отредактировано: Qraizer -
                            Цитата JoeUser @
                            А не в дебри ли Си-шной либы тебя понесло?
                            Нормально :)

                            Цитата JoeUser @
                            lround, llround, ... есличо.
                            Да, это тоже интересно и в данном случае довольно уместно :)

                            Цитата Qraizer @
                            Jin X, используй long double.
                            Прикол в том, что мне нужно было извлечь корень из большого целого числа, получив целый результат (теорема Пифагора: x = sqrt(k*k - y*y)).
                            Тогда что же получается? x = std::llround((long double)(k*k-y*y)) ?
                              Ну да. x = std::round(std::sqrt(static_cast<long double>...)) Можно без std::. Наверное. Ну и чисто C-шные функции тоже допустимы конечно. С префиксами/постфиксами которые.
                              В Плюсах типы данных играют важную роль архитектуры приложения. Они в первую очередь – в отличие от C – определяют не представление данных, а свойства этих данных. Типом данных определяются свойства сущности и набор допустимых операций. Когда ты меняешь тип данных в C, ты главным образом меняешь репрезентацию сущности, когда то же ты делаешь в C++, ты меняешь её свойства. Не, бывает, конечно, что каст в C++ используется для смены репрезентации, куда ж без этого, но философия языка всё-таки заточена под высокий уровень абстракции. Отсюда пошёл тезис про то, что если в C++ программе много кастов, это плохая программа. Просто надо понимать, что не в самих кастах дело, не они как таковые "плохие", плохо, когда тебе приходится часто и много менять свойства сущностей, с которыми ты работаешь. Это обычно говорит о плохой и непродуманной архитектуре программы.
                              В твоём случае плохо это или нет, я не знаю, тебе виднее. Но однозначно, что тебе нужно сменить свойство "целочисленный" сущности "число" на свойство "вещественный с высокой точностью".
                              Сообщение отредактировано: Qraizer -
                                Да, забыл sqrt я вписать (и мне trunc нужен, но не суть) :D
                                p.s. Кстати, (long long)x чем-то отличается от truncl(x) (где x - long double)?

                                Добавлено
                                Ну или там static_cast<long long>(x)...

                                Добавлено
                                Цитата Qraizer @
                                Но однозначно, что тебе нужно сменить свойство "целочисленный" сущности "число" на свойство "вещественный с высокой точностью".
                                А зачем мне считать всё в long double, когда у меня вся суть именно в получении целочисленного результата (long long/uint64_t), и только в этом одном месте используется long double? Я бы использовал с удовольствием какой-нибудь llsqrt, принимающий long long, но вроде нет такого...
                                  trunc() возвращает целую часть аргумента в математическом смысле, т.е. округляет к -∞. Простой каст к целому отбрасывает дробную, т.е. округляет к 0. Разница будет на отрицательных аргументах.
                                  XXXX_cast<type>(data) – в частности static_cast<>, всего их четыре разных – это новый стиль преобразования данных, которые в отличие от старого стиля (type)data не только говорит компилятору, что́ ты хочешь получить, но и зачем. Так, static_cast<> показывает, что ты имеешь желание получить данные с другими свойствами, определяемые их новым типом, при этом оставив сами данные иметь то же значение. Например, при касте целого 123 к вещественному 123.0 тебе важно, чтобы новые данные помимо получения новых свойств сохранили своё значение 123. Обычно это означает, что данные при этом сменят репрезентацию, т.к. бинарное представление целого и вещественного 123 сильно отличаются. Но вот если ты имеешь массив символов, а ты хочешь их рассматривать как массив целых, то static_cast<> тебе не поможет. Тебе нужно, оставив репрезентацию данных неизменной, сообщить компилятору, что они уже имеют новые нужные тебе свойства, осталось только лишь сообщить об этом компилятору. Поэтому char* ты должен кастовать к int* посредством reinterpret_cast<>. При этом важно понимать, что обычно репрезентация данных сильно зависит от реализации, а значит при порте этого кода на другую платформу можно сильно ушибиться лбом об стол. Иначе и быть не может, т.е. ты сам создал эту репрезентацию этих своих данных, так что компилятор тут тебя никак не проконтролирует.
                                  Итп. Суть в том, что сообщая компилятору не только цель, но и намерения, он сможет тебя обезопасить от ошибок. Старый стиль приведения типов этой информации компилятору не давал. К примеру, имея const int* ptr, ты запросто мог написать (long*)ptr, и компилятор молча это слопает, хотя у человека тут возникнет закономерный вопрос "что не так с const". То ли ты ошибся и забыл, что изначальные данные были константными, то ли ошибся и забыл в новом типе const написать, то ли ты не ошибся и в натуре хотел сразу два каста в одном.
                                  В целом, помимо фактора улучшенной диагностики кода (за малым исключением), что старый, что новый способы не отличаются.

                                  Добавлено
                                  Цитата Jin X @
                                  А зачем мне считать всё в long double, когда у меня вся суть именно в получении целочисленного результата (long long/uint64_t), и только в этом одном месте используется long double? Я бы использовал с удовольствием какой-нибудь llsqrt, принимающий long long, но вроде нет такого...
                                  Ну значит всё в порядке. Я ж говорю, тебе виднее.
                                  Что касается C или C++ сигнатур, тут как бы тот же смысл: если ты сменишь тип данных, т.б. их свойства, плюсовый перегруженный синопсис их подхватит и отследит самостоятельно, в крайнем случае ругнётся, а C-шный с префиксами всегда будет брать в том формате, на который единственный и рассчитан. Опять же, вопрос человеческого фактора. Насколько он тебе тут важен, знаешь только ты.
                                  Сообщение отредактировано: Qraizer -
                                    Цитата Qraizer @
                                    trunc() возвращает целую часть аргумента в математическом смысле, т.е. округляет к -∞. Простой каст к целому отбрасывает дробную, т.е. округляет к 0. Разница будет на отрицательных аргументах.
                                    Нет, ты путаешь trunc с floor (округление к -∞), т.к. trunc округляет к 0 (отбрасывая дробную часть), как и (int). Получается, разница только в типе возвращаемого значения?

                                    Цитата Qraizer @
                                    В целом, помимо фактора улучшенной диагностики кода (за малым исключением), что старый, что новый способы не отличаются.
                                    (int) = static_cast<int>, так?

                                    Цитата Qraizer @
                                    т.б.
                                    Что такое "т.б."? "То бишь"?

                                    Добавлено
                                    Насколько вообще дурным тоном считается смешивание в коде синтаксиса/инструментов/функций C и C++ ?
                                    Скажем, (int) вместо static_cast<int>, printf вместо cout, использование _getch(), malloc и пр.?
                                      Цитата Jin X @
                                      Цитата Qraizer @
                                      trunc() возвращает целую часть аргумента в математическом смысле, т.е. округляет к -∞. Простой каст к целому отбрасывает дробную, т.е. округляет к 0. Разница будет на отрицательных аргументах.
                                      Нет, ты путаешь trunc с floor (округление к -∞), т.к. trunc округляет к 0 (отбрасывая дробную часть), как и (int). Получается, разница только в типе возвращаемого значения?
                                      А. Ну да, попутал. Получается, только в типе результата, да.
                                      Цитата Jin X @
                                      (int) = static_cast<int>, так?
                                      Для стандартных типов да. Для пользовательских типов, т.б. классов, это не всегда так.
                                      Цитата Jin X @
                                      Что такое "т.б."? "То бишь"?
                                      Угу :) Ещё есть т.с. – ", так сказать,".
                                      Цитата Jin X @
                                      Насколько вообще дурным тоном считается смешивание в коде синтаксиса/инструментов/функций C и C++ ?
                                      Та как бы пофик, но с нюансами. Библиотека C является частью библиотеки C++, Стандарт это предусматривает, так что в общем не страшно, если вдруг. Но между библиотеками есть отличия, большей частью несущественные. Как бы дань совместимости со старым кодом. Но не более того, так что в новых проектах видеть старый стиль при наличии нового как-то странно. Обычно засилье легаси в новом проекте говорит о небрежности автора, что в некоторых случаях неприятно может сыграть ему в карму.
                                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                      0 пользователей:


                                      Рейтинг@Mail.ru
                                      [ Script execution time: 0,0685 ]   [ 17 queries used ]   [ Generated: 29.03.24, 05:50 GMT ]