Russian Qt Forum
Ноябрь 24, 2024, 04:05 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3   Вниз
  Печать  
Автор Тема: Как правильно сделать преобразование для целых и вещественных чисел?  (Прочитано 22484 раз)
Eten
Гость
« : Февраль 25, 2011, 08:11 »

У меня возникла проблема с преобразованием численных типов между собой, а это: int, long, long long, float, double.

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

На практике получается, что при преобразовании положительного числа, например, long long (2e20, т.е. получаем выход за границы типа int) в int на выходе получаем максимальное отрицательное число (т.е. отрицательную границу или ближе к ней), вместо положительной максимальной границы. У преобразования double в float с выходом за границы второго типа, выдает "inf". При преобразовании не превышающем границы конечного типа, значение не изменяется.

Например, тип переменной long long со значением 2e10 преобразуется в тип int со значением -1474836480, а при значении -2e10 выдает 1474836480.

Код:
    long long d1 = 2e10;
    int f1 = (int)(d1);

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

В добавок, int может иметь (скорее всего в 16-битной ОС) может иметь 2/4 байта в зависимости от ОС.
Записан
Fat-Zer
Гость
« Ответ #1 : Февраль 25, 2011, 08:37 »

могу для начала объяснить почему так происходит: при преобразовании int'ов от большего к меньшему происходит просто копирование младшей части, а при преобразование от меньших к большим копирование младшей части + "растягивание" старшего бита.

Выхода здесь я вижу только 2:
1) везде пользоваться типом нужной точности и только им.
2) если где-то нужно воспользоваться типом другой точности, то вручную проверять значения при преобразовании в/из него.

переопределять операторы для встроенных типов нельзя, а писать ради этого свои классы ИМХО не целесообразно.
Записан
Eten
Гость
« Ответ #2 : Февраль 25, 2011, 10:40 »

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

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

Я этот вопрос задаю к тому, что с целыми все просто их граничные значения высчитываются математикой, например для int (тут без разницы, 2 или 4 байта, границы будут верными на текущей ОС):
Код:
int min, max;
min = pow(2, sizeof(int)*8)/2*(-1);
max = pow(2, sizeof(int)*8)/2 - 1;

А как быть для вещественных чисел? Брать граничные значения из справочника?

Записан
brankovic
Гость
« Ответ #3 : Февраль 25, 2011, 11:13 »


Код
C++ (Qt)
#include <limits>
 
double d;
float f;
 
if (d < std::numeric_limits <float>::max () && d > std::numeric_limits <float>::min ())
 f = d;
else
 throw "out of range";
 

numeric_limits работает для всех базовых типов.

Код:
min = pow(2, sizeof(int)*8)/2*(-1);
max = pow(2, sizeof(int)*8)/2 - 1;

Так лучше не делать, легко ошибиться. Например pow(2,sizeof(int) * 8 ) даст 0. На крайний случай можно использовать max = -1u / 2; min = max + 1;
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Февраль 25, 2011, 12:09 »

Вопрос тут такой, как лучше и правильнее реализовать преобразование чисел с проверкой границ математическим способом? И не будет ли искажений в вычислениях, если я переопределю операторы преобразования, где будет стоять проверка границ с записыванием значения границы при ее превышении?
Ну преобразовали Вы 1.0e+20 в MAX_INT и толку? Что делать с "преобразованным" и как его пользовать?

Лучше всего решать это "на уровне задачи". Если напр. длина вектора меньше некоторого epsilon (часто 1.0e-5f), то нормировать его нельзя, это крайний случай который должен отрабатываться. Также если в расчетах значения начинают превышать некоторое значение (типа 1.0e+10) - это просто значит что расчеты идут вразнос и на следующем шаге уже получим nan. Это должно пресекаться подсечкой или остановкой расчетов.

Все работает "с какой-то точностью и в каких-то пределах", и задача которую Вы ставите непонятна.
Записан
Eten
Гость
« Ответ #5 : Февраль 25, 2011, 12:11 »

numeric_limits работает для всех базовых типов.
Проверил, действительно работает! У меня тут маленький вопрос. Могу ли я не беспокоиться об учете особенности int с 2/4 байта в памяти?

Так лучше не делать, легко ошибиться. Например pow(2,sizeof(int) * 8 ) даст 0. На крайний случай можно использовать max = -1u / 2; min = max + 1;
pow(2,sizeof(int) * 8 ), у меня честно говоря выдало не ноль, а std::numeric_limits<int>::max(). Но то, что этот вариант превышает границы типа int (точнее можно столкнуться с такой ситуацией), это да.
Записан
Eten
Гость
« Ответ #6 : Февраль 25, 2011, 12:32 »

Ну преобразовали Вы 1.0e+20 в MAX_INT и толку? Что делать с "преобразованным" и как его пользовать?

Лучше всего решать это "на уровне задачи". Если напр. длина вектора меньше некоторого epsilon (часто 1.0e-5f), то нормировать его нельзя, это крайний случай который должен отрабатываться. Также если в расчетах значения начинают превышать некоторое значение (типа 1.0e+10) - это просто значит что расчеты идут вразнос и на следующем шаге уже получим nan. Это должно пресекаться подсечкой или остановкой расчетов.

Все работает "с какой-то точностью и в каких-то пределах", и задача которую Вы ставите непонятна.
Не все ситуации требуют присечения. У меня, например, больше нужда проконтролировать, чтобы не выходило за границы. Тем более, получить MAX_INT в своих действиях будет наглядней свидетельствовать, о превышении границы. А в некоторых случаях нужно перевести из большего в меньший с усеканием до макс. границы меньшего типа.

Т.е. у меня есть класс NNumeric (он целесообразен, случае с моим проигрывателем и в предметной области Интерактивная Литература). NNumeric хранит значения чисел во float, но есть нужда выдавать/работать его/с ним и в целых типах, точнее он работает, либо как целый тип, либо как вещественный тип float, потому, что float превышает границы int и long, то мне хватает одного его для хранения (long и float оба занимают по 4 байта) значения. Т.о. этот класс реализован для хранения значений переменных и чисел (константых значений) для хранения и обработки этих значений, поступающих на вход исполнителя посредством формального языка.  Строит глазки

Учитывая абзац выше, мне удобнее, не приостанавливать программу (тем более, что предметная область не располагает к описанным вами математическим расчетам и по сложнее), а записать ту границу типа, которое значение превышает. А также просто вести контроль границ, чтобы не было останова из-за превышения границ во время исполнения программы и последующего ее вылета.  Подмигивающий

З.Ы.
Мне бы не хотелось выходить за тему, дальше двух абзацев выше. Написал, только лишь для ответа на вопрос Igors-а.  Подмигивающий
« Последнее редактирование: Февраль 25, 2011, 12:35 от Eten » Записан
brankovic
Гость
« Ответ #7 : Февраль 25, 2011, 12:35 »

Проверил, действительно работает! У меня тут маленький вопрос. Могу ли я не беспокоиться об учете особенности int с 2/4 байта в памяти?

Будьте покойны, numeric_limits для этого и придуман, не подведёт Улыбающийся

pow(2,sizeof(int) * 8 ), у меня честно говоря выдало не ноль, а std::numeric_limits<int>::max(). Но то, что этот вариант превышает границы типа int (точнее можно столкнуться с такой ситуацией), это да.

Да, для pow из math.h всё правильно. Я думал самодельный int pow (int, int).
Записан
Eten
Гость
« Ответ #8 : Февраль 27, 2011, 21:09 »

Кхм, столкнулся с интересной ситуацией. С целыми числами у меня получилось, но с вещественными все оказалось куда сложнее. Точнее говоря, я и для них проверку написал (см. код ниже), но меня смутил один момент. Минимальная граница отличается от заявленной в справочниках по Си++ (точнее она в половину меньше числа для максимума, но с 'e-38'). Как тут быть?

Собственно, код:
Код:
#include <limits>

    inline float floatborders(float Value)
    {
        if (Value < std::numeric_limits<float>::max()*(-1))
            return std::numeric_limits<float>::max()*(-1);

        if (Value > std::numeric_limits<float>::max())
            return std::numeric_limits<float>::max();

        if (Value > std::numeric_limits<float>::min()*(-1) && Value > -1 && Value < 0)
            return std::numeric_limits<float>::min()*(-1);

        if (Value < std::numeric_limits<float>::min() && Value < 1 && Value > 0)
            return std::numeric_limits<float>::min();

        return Value;
    }
Первых два условия проверяют, как и в случае для целых чисел, максимальные полож. и отриц. границы. А два последних условия проверяют максимальное и минимальное значение для чисел между -1..0 и 0..1, т.е. для чисел после запятой.

При максимуме я получил ~+/-3.4e38, а при минимуме я получил ~+/-1.7e-38 (в справочниках было +/-3.4e-38).

В общем, я все правильно написал или что-то упустил из внимания?
Записан
Fat-Zer
Гость
« Ответ #9 : Февраль 27, 2011, 21:38 »

значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  Улыбающийся

а так вроде всё правильно... правда в функцию наверное double следовало бы передавать, а то так вы только на INF/NAN проверяете...
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Февраль 27, 2011, 21:58 »

Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify
Записан
Fat-Zer
Гость
« Ответ #11 : Февраль 27, 2011, 22:19 »

Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify
не NAN конечно... денормализованное число(или как-то так, не помню точно, как оно называется)
Записан
Eten
Гость
« Ответ #12 : Февраль 27, 2011, 22:49 »

значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  Улыбающийся

а так вроде всё правильно... правда в функцию наверное double следовало бы передавать, а то так вы только на INF/NAN проверяете...
Мне всегда INF попадалось при переходе за границы и все работало, т.е. сравнения шли и границы записывались. А в чем собственно различие INF и NAN?

Мне как-то не посчастливилось найти нормальное описание INF и NAN в СИ++, можете дать отсылку к такому материалу?  Строит глазки
Записан
Eten
Гость
« Ответ #13 : Февраль 27, 2011, 22:50 »

Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify
А можно наглядный в виде кода привести? До применения подобного еще не доходил.  Подмигивающий
Записан
Fat-Zer
Гость
« Ответ #14 : Февраль 27, 2011, 23:27 »

значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  Улыбающийся

а так вроде всё правильно... правда в функцию наверное double следовало бы передавать, а то так вы только на INF/NAN проверяете...
Мне всегда INF попадалось при переходе за границы и все работало, т.е. сравнения шли и границы записывались. А в чем собственно различие INF и NAN?

Мне как-то не посчастливилось найти нормальное описание INF и NAN в СИ++, можете дать отсылку к такому материалу?  Строит глазки
это не дело Си/С++, это дело общего стандарта(IEEE754). Вот  перевод на русский. Там вообще много всего интересного. то о чём мы говорили(nan и inf) расписываются в п. 6.
про то как проще всего получить их на Си:
Код
C
#include <cstdio>
 
int main(int , char *[])
{
float f=1;
f=0;
f=1/f;
printf("INF: %e\n",f);
f=0;
f=0/f;
printf("NAN: %e\n",f);
f=0;
f=-1/f;
printf("-INF: %e\n",f);
 
return 0;
}
 

[added]
почитал там примеры ошибок... как же страшно жить, работая с плавующей точкой...
« Последнее редактирование: Февраль 27, 2011, 23:42 от Fat-Zer » Записан
Страниц: [1] 2 3   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.161 секунд. Запросов: 22.