Russian Qt Forum

Программирование => С/C++ => Тема начата: Eten от Февраль 25, 2011, 08:11



Название: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: 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 байта в зависимости от ОС.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 25, 2011, 08:37
могу для начала объяснить почему так происходит: при преобразовании int'ов от большего к меньшему происходит просто копирование младшей части, а при преобразование от меньших к большим копирование младшей части + "растягивание" старшего бита.

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

переопределять операторы для встроенных типов нельзя, а писать ради этого свои классы ИМХО не целесообразно.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 25, 2011, 10:40
Хм, понятно. Значит самый простой вариант, при необходимости использовать разные типы чисел, это проверять границы и вручную контролировать этот процесс, как мы это делаем в массивах, раз внутри границ конечного типа все работает как надо.

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

Я этот вопрос задаю к тому, что с целыми все просто их граничные значения высчитываются математикой, например для int (http://udev.nnov.ru/publ/cc/osnovy/vstroennye_tipy_dannykh/5-1-0-9#table.6.1) (тут без разницы, 2 или 4 байта, границы будут верными на текущей ОС):
Код:
int min, max;
min = pow(2, sizeof(int)*8)/2*(-1);
max = pow(2, sizeof(int)*8)/2 - 1;

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



Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: brankovic от Февраль 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;


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Февраль 25, 2011, 12:09
Вопрос тут такой, как лучше и правильнее реализовать преобразование чисел с проверкой границ математическим способом? И не будет ли искажений в вычислениях, если я переопределю операторы преобразования, где будет стоять проверка границ с записыванием значения границы при ее превышении?
Ну преобразовали Вы 1.0e+20 в MAX_INT и толку? Что делать с "преобразованным" и как его пользовать?

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

Все работает "с какой-то точностью и в каких-то пределах", и задача которую Вы ставите непонятна.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 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 (точнее можно столкнуться с такой ситуацией), это да.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 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-а.  ;)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: brankovic от Февраль 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).


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 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).

В общем, я все правильно написал или что-то упустил из внимания?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 27, 2011, 21:38
значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  :)

а так вроде всё правильно... правда в функцию наверное double следовало бы передавать, а то так вы только на INF/NAN проверяете...


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Февраль 27, 2011, 21:58
Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 27, 2011, 22:19
Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify
не NAN конечно... денормализованное число(или как-то так, не помню точно, как оно называется)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 27, 2011, 22:49
значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  :)

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

Мне как-то не посчастливилось найти нормальное описание INF и NAN в СИ++, можете дать отсылку к такому материалу?  ::)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 27, 2011, 22:50
Для флотов вся эта песня лишена смысла, т.к, если value = nan. то любое сравнение вернет false.
Надо задействовать fpclassify
А можно наглядный в виде кода привести? До применения подобного еще не доходил.  ;)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 27, 2011, 23:27
значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  :)

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

Мне как-то не посчастливилось найти нормальное описание INF и NAN в СИ++, можете дать отсылку к такому материалу?  ::)
это не дело Си/С++, это дело общего стандарта(IEEE754). Вот (http://www.softelectro.ru/ieee754.html)  перевод на русский. Там вообще много всего интересного. то о чём мы говорили(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]
почитал там примеры ошибок... как же страшно жить, работая с плавующей точкой...


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 05:53
значит в книжке ошибка... такое часто бывает... да и какая разница. в 2 раза точнее, не в 2 раза хуже  :)
А есть ли различие на Windows, MAC, Linux и мобильных системах в размерности с плавающей точкой? Если есть, может кто привести их значения для MAC и Linux (У меня было описано результаты на Windows)?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 06:15
это не дело Си/С++, это дело общего стандарта(IEEE754). Вот (http://www.softelectro.ru/ieee754.html)  перевод на русский. Там вообще много всего интересного. то о чём мы говорили(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]
почитал там примеры ошибок... как же страшно жить, работая с плавующей точкой...

Как я понял из ваших примеров Nan это ситуация деления на ноль, ясное дело что такое не допустимо (как например с логарифмами и квадратным корнем, но там проверяется без нас и выводит соответствующие значение). Другое дело INF. Тем более у меня на Qt под Windows, в программе хоть и пишет +/-INF, но видимо каким то образом помнит значения, т.к. проверка на границы у меня срабатывает на ура, поэтому не было нужды брать тип размерностью выше (а уж если нужно, то как вообще тогда проверять самый максимальный вещественный тип чисел). К тому же подобное использование выхода за пределы с inf не сработает с целочисленными типами, но зато все это можно провернуть присвоив значение изначально переменной с типом float (видел такое у многих), но с проверкой границ для целочисленного типа, потом можно спокойно преобразовать эту переменную в этот целочисленный тип. Вот пример кода ниже, который кстати тоже работает.
Код:
            long arg0, arg1=std::numeric_limits<long>::max(), arg2=std::numeric_limits<long>::max();
            float res = float(arg1) + float(arg2);
            if (res >= std::numeric_limits<long>::max())
                arg0 = std::numeric_limits<long>::max();
            else
                if (res <= std::numeric_limits<long>::min())
                    arg0 = std::numeric_limits<long>::min();
                else
                    arg0 = long(res);
Тут даже, если переменная res станет inf, проверки границы все равно проверятся и присваиваются соответствующие значения. Проверял, что предыдущий пример кода, что код выше в этом посте, все работало, как надо. Igors, как я понял Nan это сродни ошибка (деление на ноль, например), если да, то достаточно допустить запись nan, либо выдавать сообщение об ошибке и делать останов программы в случае необходимости (хотя лучше сделать этой тихой ошибкой, чем рушить программу, но зависит от ТЗ программы). Igors, я не упустил никаких подводных камней? И что мне даст использование fpclassify?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 07:16
До меня кажется дошло о чем говорил Igors, когда затронул тему про Nan (не числа). Вот здесь  (http://www.softelectro.ru/ieee754.html)в п.7.2, как раз изображен Полный диапазон чисел одинарной точности (32 бит) по стандарту IEEE754 с достаточным пояснением, чтобы можно было понять в чем собака зарыта. Но для лучшего понимания стоит ознакомиться с п. 5 и 6.  ;)

З.Ы.
Пойду, пока подумаю еще, если ответите на мои вопросы, заранее спасибо.  :)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 08:51
Не знаю как у других, но задавая шестнадцатеричным форматом я не получал тех же чисел. Но, несмотря на это, с помощью numeric_limits<float> смог найти все нужные мне значения по стандарту IEEE754, необходимые для проверки границ (кстати в месте указанном выше, есть те самые реальные границы, которые и используются, а не как указано в справочниках). NAN я в расчет не беру, т.к. NAN - это Не числа NAN(No a Numbers). К ним относятся символы, или результаты недопустимых операций, поэтому тут нужно делать проверку на ошибки, а не на проверку границ, т.е. выходит за рамки проверок границ. Но это уже другая тема или ее часть. В абзаце ниже я поясню на пальцах, почему можно использовать один float для проверки границ целочисленных типов до long int (32 бита, за 64 бита говорить не берусь) и сам тип float (также равносильно и для double и long double, т.е. в плане проверки самих себя).

Для начала стоит сказать о структуре по битового построения двоичного формата с одинарной точностью, но это уже сделано до меня и можно узнать об этом здесь  (http://www.softelectro.ru/ieee754.html)в п.7.2. И хорошенько по размыслив и поэкспериментировав с числами можно придти к следующим выводам. Во-первых, в отличии от целочисленных типов, вещественные не используют по максимуму свои байты, а хранят значения в определенном формате, позволяющем определить все ситуации для работы с числами с плавающей запятой. Во-вторых, понятие NAN (не числа) относится к символам или недопустимым операциям (например, деление на ноль), здесь стоит делать обработку на ошибки, т.к. к проверкам на граничные значения NAN никакого прямого отношения не имеет, разве что косвенное (но уже математика на уровне Mathcad-а, могу ошибаться, но опираясь на практику можно полагать, как было сказано). В-третьих, если значение превышает граничные значения положительного максимального нормализованного, то ему присваивается положительная бесконечность (для отрицательных, отрицательная бесконечность и проверка, как для отрицательных чисел). В-четвертых, если значение меньше положительного денормализованного минимума, то ему присваивают +0 (могу ошибаться, но вроде так, по крайней мере +inf не присваивают, а присваивают 0, из-за этого и такие выводы ::) ) (опять же в случае отрицательных присваивают -0). Но, учет выхода за границы минимального денормализованного значения полезно для программ с инженерными расчетами (например для Mathcad-а), для обычных операций достаточно ориентироваться на нормализованное минимальное значение.

В итоге получаем, что тип float, как и любой вещественный тип реализованный по стандарту IEEE75, не выходит (пробовал я ему записывать число +/-2e208, получил значение +/-inf) за границы своих диапазонов (учитываем формат записи +/-inf и +/-0), но для нормального приведения чисел к границам нам надо писать свои проверки границ, чего нам хватает numeric_limits<float> (кстати, я так и не смог найти денормализованного максимального значения, но для проверки границ это не так сущевственно). В случае значения NAN, разговор отдельный, т.к. этот момент требует отлавливания проверки ошибки (или ошибка возникнет сама с остановом программы) на символы и недопустимые операции исходя из самого определения NAN. Поэтому учитывать его при проверки границ или не учитывать это ваш выбор в зависимости от ваших потребностей.  Иначе говоря, мы можем использовать вещественный тип для границ, как для самого типа, так и для целого входящего своей размерностью в этот вещественный тип (например, long int входит в float). Но проверки на значение на NAN стоит делать до проверки границ.

А теперь, собственно рабочий код для проверки границ (на NAN тут не проверяется). Для использования numeric_limits нужно подключать "limits", т.е. #include <limits>.

Код для проверки границ типа long, через float:
Код:
            long arg0, arg1=std::numeric_limits<long>::max(), arg2=std::numeric_limits<long>::max();
            float res = float(arg1) + float(arg2);
            if (res >= std::numeric_limits<long>::max())
                arg0 = std::numeric_limits<long>::max();
            else
                if (res <= std::numeric_limits<long>::min())
                    arg0 = std::numeric_limits<long>::min();
                else
                    arg0 = long(res);

Пример функции для проверки границ (с нормализованными граничными значениями):
Код:
    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;
    }

Примечание: Функции привел как есть, поэтому если кому насущна оптимизация, то вам ее нужно сделать самим, я же предпочел выложить в удобочитаемом виде.

З.Ы.
Не знаю, что может еще выясниться с этим числами с плавающей запятой, но полагаю, что на свой вопрос этим постом я дал полный и развернуты ответ.  Если в чем-то ошибаюсь, то просьба поправить с объяснениями. ;)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 28, 2011, 12:48
Цитировать
Как я понял из ваших примеров Nan это ситуация деления на ноль, ясное дело что такое не допустимо.
поправлю: тфт деление нуля на ноль. и прочий бред типа ноль в нулевой степени, а в общем случае деление на 0 даёт +-inf. Всё это на x86 на других архитектурах FPU может вести себя по-другому. И даже на x86 сопроцессор может возбудить исключение при делении на 0.(правда у меня такого добиться не получалось, мысль пришла в голову при рассматрении пятого бита CR0)
Цитировать
Код для проверки границ типа long, через float:
не стал бы я так смело мешать целочисленную и FPU арифметику... наверняка можно подобрать значения на грани с ошибкой округления. в ассеблере(для x86) для этих целей есть инструкции jo. А вот как простым способом в языках высокого уровня проверять уже не помню...
Цитировать
Не знаю как у других, но задавая шестнадцатеричным форматом я не получал тех же чисел.
то, что x86 little-endian не забыли учесть?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Февраль 28, 2011, 14:19
А можно наглядный в виде кода привести? До применения подобного еще не доходил.  ;)
Код
C++ (Qt)
double bad = sqrt(-1.0); // провоцируем nan
if (!(bad >= 0) && !(bad < 0))     // кажется бессмысленным, но
printf("wrong float %g\n", bad);  // поймали nan
 


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: m_ax от Февраль 28, 2011, 14:57
Если есть такая потребность в задаче делать подобные проверки (согласитесь не страндартный случай), то может просто написать свой класс мега астрономических чисел, который знал бы такие вещи как деление на ноль и всё такое? И вёл бы себя как обычное число, был бы совместим с double.
Почему нет?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Февраль 28, 2011, 15:26
Eten, отвечая на Ваши вопросы

- случай nan всегда надо отрабатывать отдельно, специальной проверкой. Так как все арифметические операции с nan дают в итоге тоже nan, он размножается очень быстро.
Примечание: не связывайтесь с ф-цией isNan, там есть заморочки, поэтому программисты используют приемчик что приведен выше.

- как Вы уже разобрались, характеристика float может оказаться недостаточной (INF/DENOM), число > e+38 или < e-38 (но не ноль). Это можно узнать с помощью fpclassify, нет никакого смысла проверяться по std::limits

- как (еще раз) указал m_ax, надо поработать с постановкой. Вы конечно можете считать INF "нормальным", но следующая операция с ним легко может породить nan - зачем же пускать козла в огород? Понятно что специфика Вашей задачи за рамками этого обсуждения, но в реальных расчетах пределы устанавливаются волевым решением и они намного меньше максимальных. Напр 1.0e+20 еще далеко до предела, но в квадрат это число уже не возвести. Часто надо учесть что от мантиссы могут остаться "рожки да ножки", т.е. знаки после запятой теряются если замешаны большие значения.

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



Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Fat-Zer от Февраль 28, 2011, 16:53
про отлов nan, ещё более интересное условие:
Код
C++ (Qt)
if (!(bad==bad))  
printf("wrong float %g\n", bad);  
 


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 17:55
...

- как (еще раз) указал m_ax, надо поработать с постановкой. Вы конечно можете считать INF "нормальным", но следующая операция с ним легко может породить nan - зачем же пускать козла в огород? Понятно что специфика Вашей задачи за рамками этого обсуждения, но в реальных расчетах пределы устанавливаются волевым решением и они намного меньше максимальных. Напр 1.0e+20 еще далеко до предела, но в квадрат это число уже не возвести. Часто надо учесть что от мантиссы могут остаться "рожки да ножки", т.е. знаки после запятой теряются если замешаны большие значения.

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

Полагаю стоит внести сюда немного практичности и конкретики, т.к. я хочу довести свой класс NNumeric (см. ниже) до разумных границ. Его задача, это хранение целочисленного или с плавающей запятой значения, а также сопровождающей ее информации, нужно для работы с классом в местах его хранения, при выполнении математики и просто обращения с ним, как с обычным типом, но в разумных пределах. Т.е. у него есть два автоматических конструктора для типов float и long (собственно, с которыми он и работает), конструктор по умолчанию и конструктор копирования. Несколько функций позволяющих изменять отдельные значения в объекте. Плюс операторы преобразования в типы float, double, int, long; операторы инкремента и декремента, оператор присвоения объекта того же класса, а также операторы сравнения. Все это мне нужно для создания типа "Числовой", который может работать, либо как целочисленный тип (long), либо как вещественный (float). Я ухватился за float и long потому, что они оба занимают по 4 байта, а для меня это весомый фактор в выборе типов. Экземпляры класса NNumeric используются у меня для хранения временных переменных (в мат. операциях), хранения переменных и константных значений в проигрывателе (можно подразумевать исполнитель входного кода). 4 байта насущны для меня из-за записи/чтения самого значения из произвольного файла. Сам класс NNumeric  почти обкатан, но вот проверка границ для него не маловажна (я тока еще на погрешности учет не ввел для суммы и разности чисел, там при разнице между числами 232 появляются погрешности). Т.к. тип long (учитывая про чтение из файла) не может быть превышен его задании (вручную можно, в моей проге нет места ручному использованию этого класса), то тут проверку на границы я не ставлю, а произвожу ее в функции "математических операций и функций" используя тип float (например, не получится сделать проверку на границы, если оба целочисленных типа будут иметь максимальное положительное значение при выполнении ариф. операции "+" между ними). В математике NNumeric используется для работы с простыми арифметическими операциями (+, -, /, *), квадр. корень, степень, логарифмы (e и 10), модуль числа, мин и макс, целочисленное деление и остаток от него, рандом от 0 и до N/1, exp, sin, cos, tg, ctg, arccos, arcsin, arctg, arcctg, выделение дробной и целой части, а также три вида окргления (round, floor, ceil).

Сам класс NNumeric:
Код:
#include <limits>
#include <cmath>


class NNumeric{
public:
    inline NNumeric() {this->data._isreal = true; this->data._value = 0; this->data._id = -1; this->data._constvalue = false; this->data._name = "";}
    inline NNumeric(float Value) {this->data._isreal = true; this->setValue(floatborders(Value)); this->data._id = -1; this->data._constvalue = false; this->data._name = "";}
    inline NNumeric(long Value) {this->data._isreal = false; this->setValue(Value); this->data._id = -1; this->data._constvalue = false; this->data._name = "";}
    inline NNumeric(const NNumeric& a) {this->setConstValue(a.data._constvalue); this->setID(a.data._id); this->setName(a.data._name); this->setIsReal(a.data._isreal); this->setValue(a.data._value);}

    inline float round() {return std::floor(this->Value() + 0.5);}
    inline float fractional() {return this->Value() < 0 ? this->Value() - std::ceil(this->Value()) : this->Value() - std::floor(this->Value());}
    inline float integer() {return this->Value() < 0 ? std::ceil(this->Value()) : std::floor(this->Value());}

    inline int ID() {return this->data._id;}
    inline void setID(int ID) {this->data._id = ID;}
    inline bool ConstValue() {return this->data._constvalue;}
    inline void setConstValue(bool ConstValue) {this->data._constvalue = ConstValue;}
    inline QString Name() {return this->data._name;}
    inline void setName(QString Name) {this->data._name = Name;}
    inline void setIsReal(bool IsReal) {this->data._isreal = IsReal;}
    //Если ложно, то значение принимается и выдается (при печати числа на экране)
    // с отсечение дробной части числа и соблюдением границ типа long int.
    inline bool IsReal() const {return this->data._isreal;}
    inline void setValue(float Value)
    {
        if (!this->data._isreal)
        {
            if (Value >= std::numeric_limits<long>::max())
                Value = std::numeric_limits<long>::max();
            else
                if (Value <= std::numeric_limits<long>::min())
                    Value = std::numeric_limits<long>::min();
                else
                    Value = long(Value);
        }
        this->data._value = Value;
    }
    inline float Value() const {return this->data._value;}

    inline void operator=(const NNumeric& a) {this->setConstValue(a.data._constvalue); this->setID(a.data._id); this->setName(a.data._name); this->setIsReal(a.data._isreal); this->setValue(a.data._value);}
    inline operator QString() {return QString::number(this->data._value);}
    inline operator float() {return float(this->data._value);}
    inline operator double() {return double(this->data._value);}
    inline operator int() {if (this->Value() < std::numeric_limits<int>::min()) return std::numeric_limits<int>::min(); if (this->Value() > std::numeric_limits<int>::max()) return std::numeric_limits<int>::max(); return int(this->Value());}
    inline operator long() {if (this->Value() < std::numeric_limits<long>::min()) return std::numeric_limits<long>::min(); if (this->Value() > std::numeric_limits<long>::max()) return std::numeric_limits<long>::max(); return long(this->Value());}
    inline void operator++(int) {this->data._value + 1 >= std::numeric_limits<long>::max() ? this->data._value = std::numeric_limits<long>::max() : this->data._value++;}
    inline void operator--(int) {this->data._value - 1 <= std::numeric_limits<long>::min() ? this->data._value = std::numeric_limits<long>::min() : this->data._value--;}
    inline bool operator==(const NNumeric& a) {return this->data._value == a.Value();}
    inline bool operator!=(const NNumeric& a) {return this->data._value != a.Value();}
    inline bool operator&&(const NNumeric& a) {return this->data._value && a.Value();}
    inline bool operator||(const NNumeric& a) {return this->data._value || a.Value();}
    inline bool operator<(const NNumeric& a) {return this->data._value < a.Value();}
    inline bool operator<=(const NNumeric& a) {return this->data._value <= a.Value();}
    inline bool operator>(const NNumeric& a) {return this->data._value > a.Value();}
    inline bool operator>=(const NNumeric& a) {return this->data._value >= a.Value();}

private:

    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;
    }

    struct NData {
    //если идентификатор равен -1, значит это либо временная переменная, либо константное значение
    int _id;
    //константные или временные значения имен не имеют, только переменные.
    QString _name;
    float _value;
    bool _isreal;
    //это указывает на константное значение
    bool _constvalue;
    };
    NData data;
};

Сама функция, где выполняются математические операторы:
Код:
void result::MathematicalOperations(ushort Modifikator)
{
    NNumeric arg0, arg1, arg2;
    switch (Modifikator)
    {
        case 2:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
            {
                 float res = float(arg1) + float(arg2);
                 if (res >= std::numeric_limits<long>::max())
                     arg0 = std::numeric_limits<long>::max();
                 else
                     if (res <= std::numeric_limits<long>::min())
                         arg0 = std::numeric_limits<long>::min();
                     else
                         arg0 = long(res);
            }
            else
                 arg0 = float(arg1) + float(arg2);

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 3:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
            {
                float res = float(arg1) - float(arg2);
                if (res <= std::numeric_limits<long>::min())
                    arg0 = std::numeric_limits<long>::min();
                else
                    if (res >= std::numeric_limits<long>::max())
                        arg0 = std::numeric_limits<long>::max();
                    else
                        arg0 = long(res);
            }
            else
                 arg0 = float(arg1) - float(arg2);

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 4:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
            {
                 float res = float(arg1) * float(arg2);
                 if (res >= std::numeric_limits<long>::max())
                     arg0 = std::numeric_limits<long>::max();
                 else
                     if (res <= std::numeric_limits<long>::min())
                         arg0 = std::numeric_limits<long>::min();
                     else
                         arg0 = long(res);
            }
            else
                 arg0 = float(arg1) * float(arg2);

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 5:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
                 arg0 = long(arg1) / long(arg2);
            else
                 arg0 = float(arg1) / float(arg2);

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 6:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0++;
            arg0.setID(-1);
            arg0.setConstValue(false);
            arg0.setName("");

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 7:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0--;
            arg0.setID(-1);
            arg0.setConstValue(false);
            arg0.setName("");

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 8:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = (ldiv(arg1, arg2)).quot;

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 9:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = (ldiv(arg1, arg2)).rem;

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 10:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
            {
                 float res = std::pow(float(arg1),int(arg2));
                 if (res > std::numeric_limits<long>::max())
                     arg0 = std::numeric_limits<long>::max();
                 else
                     if (res <= std::numeric_limits<long>::min())
                         arg0 = std::numeric_limits<long>::min();
                     else
                         arg0 = long(res);
            }
            else
                arg0 = std::pow(float(arg1),float(arg2));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 11:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg0.IsReal())
                arg0 = long(std::sqrt(float(arg0)));
            else
                arg0 = std::sqrt(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 12:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::fabs(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 13:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
                if (long(arg1) < long(arg2))
                    arg0 = arg1;
                else
                    arg0 = arg2;
            else
                if (float(arg1) < float(arg2))
                    arg0 = arg1;
                else
                    arg0 = arg2;

            arg0.setID(-1);
            arg0.setConstValue(false);
            arg0.setName("");

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 14:
            arg2 = (this->StackValue.pop()).value<NNumeric>();
            arg1 = (this->StackValue.pop()).value<NNumeric>();

            if (!arg1.IsReal() && !arg2.IsReal())
                if (long(arg1) > long(arg2))
                    arg0 = arg1;
                else
                    arg0 = arg2;
            else
                if (float(arg1) > float(arg2))
                    arg0 = arg1;
                else
                    arg0 = arg2;

            arg0.setID(-1);
            arg0.setConstValue(false);
            arg0.setName("");

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 15:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            srand ( time(NULL) );
            arg0 = long(rand() % long(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 16:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::log10(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 17:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::log(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 18:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::exp(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 19:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::cos(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 20:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::sin(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 21:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::tan(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 22:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = 1.0f/std::tan(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 23:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::acos(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 24:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::asin(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 25:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::atan(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 26:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = 1.0f/std::atan(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 27:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::floor(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 28:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = std::ceil(float(arg0));

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 29:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = arg0.round();

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 30:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = arg0.fractional();

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 31:
            arg0 = (this->StackValue.pop()).value<NNumeric>();

            arg0 = arg0.integer();

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
        case 32:

            srand ( time(NULL) );
            arg0 = float(rand() % 1001)/1000.0f;

            this->StackValue.push(QVariant::fromValue(arg0));
        break;
    }
}

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

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

З.Ы.
Надеюсь я не переборщил с постом. Но это все, что использует числа и в той или иной мере затрагивает вопрос о границах.  ::)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Февраль 28, 2011, 18:02
Пардон, забыл кое-что к коду с функцией операций приложить:
Код:
Модификатор	Комментарий
2 +
3 -
4 *
5 /
6 ++
7 --
8 целочисленное деление
9 % (остаток от целочисленного деления)
10 ^
11 квадратный корень
12 модуль числа
13 минимальное число
14 максимальное число
15 целочисленный рандом от 0 и до m-1
16 log10
17 ln
18 exp
19 cos
20 sin
21 tg
22 ctg
23 arccos
24 arcsin
25 arctg
26 arcctg
27 округление в сторону нуля до целого
28 округление в большую сторону до целого
29 округление до целого
30 выделение дробной части
31 отсечение дробной части
32 рандом от 0 до 1

Число, соответсвует значению case  в условии.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: m_ax от Февраль 28, 2011, 18:40
Ёлки зелёные))

И всё же почему в классе NNumeric не хранить переменную в double? Всё равно NNumeric у вас весит больше 4 байт.
А вместо операций, например в такой:
Код
C++ (Qt)
inline float Value() const {return this->data._value;}
 
 
делать следующее:
Код
C++ (Qt)
inline float Value() const {
   double d = fabs(this->data._value);
   bool sign = (this->data._value >= 0.0);
   if (d > std::numeric_limits<float>::max())
       return (sign) ? std::numeric_limits<float>::max() : -std::numeric_limits<float>::max();
   if (sign)
        return (d > std::numeric_limits<float>::min()) ? float(d) : float(0);
   return (d > std::numeric_limits<float>::min()) ? float(-d) : float(0);
}
 
double то Вам точно хватит)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Февраль 28, 2011, 19:12
про отлов nan, ещё более интересное условие:
Код
C++ (Qt)
if (!(bad==bad))  
printf("wrong float %g\n", bad);  
 
То да, но не стоит так дразнить компилятор  :)

2Eten Пока не забыл - срочно избавьтесь от long, на моей платформе (Mac) он 4 байта только в 32-бит, но 8 в 64-бит. Если sizeof важен, используйте "явный" тип, напр. SInt32 (для Mac и Linuх он родной, а на Вындоуз заряжайте через typedef)

И всё же почему в классе NNumeric не хранить переменную в double? Всё равно NNumeric у вас весит больше 4 байт.
Как я понял, Eten крутит что-то типа своего интепретатора, ему нужно не просто "расчеты" а еще и с учетом типа. Какие-то операции пройдут для флотов, но не должны для int - так задумано

Код:
    if (d > std::numeric_limits<float>::max())
А как это должно работать?  :) Если d = nan, то проверка не сработает. И что то за число больше максимального?

Мое предложение: вставить проверки на валидность и выбрасывать exception, напр так (псевдокод)

Код
C++ (Qt)
// static
void NNumeric::ValidateOperation( int operation, const NNumeric & arg1, const NNumeric & arg2 )
{
 if (arg1.Nan()) throw "arg1 is nan";  // стоп, с nan ничего доброго не выйдет
 if (arg2.Nan()) throw "arg2 is nan";  
 
// для конкретных операций
 switch (operation) {
  case NNumeric::operation_divide:
   if (!arg2.CanBeDivider()) throw "ZeroDivide";
   break;
   ...
 }
}
 


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 01, 2011, 05:56
И всё же почему в классе NNumeric не хранить переменную в double? Всё равно NNumeric у вас весит больше 4 байт.
Ээм, в принципе для меня не страшно хранить больше 4 байт, важно, чтобы float в Windows, Mac и Linux в файл записывал 4 байта. А вот насчет типа double вы правы, особенно если учитывать различия в ситуациях c +/-inf, то в таком случае очень просто делать проверки на inf и nan. У меня только один вопрос. Сколько занимает байт тип float в ОС Windows, Mac и Linux? Надеюсь я не выйду за границы 4 байт, хотя по стандарту так и должно быть 4 байта (т.е. 32 бита).

Цитата: Igors
2Eten Пока не забыл - срочно избавьтесь от long, на моей платформе (Mac) он 4 байта только в 32-бит, но 8 в 64-бит. Если sizeof важен, используйте "явный" тип, напр. SInt32 (для Mac и Linuх он родной, а на Вындоуз заряжайте через typedef)
Кхм, кхм, а как мне в Си++ указать SInt32 так, чтобы на всех трех ОС оно занимало по 4 байта? Т.к. я пишу на Qt, то мне хватить qint32, но не возникнет ли проблемы с использованием std::numeric_limits<qint32>?

Цитата: Igors
Как я понял, Eten крутит что-то типа своего интепретатора, ему нужно не просто "расчеты" а еще и с учетом типа. Какие-то операции пройдут для флотов, но не должны для int - так задумано
Дело не в типе исполнителя, а в том, что мне нужно сохранить промежуточный тип (целый или вещественный). Если рассмотреть ситуацию суммы двух argX (X - номер) и то, что операция суммы и присвоения это две разных операций, то очень сохранить тип временной переменной (опять же вопрос границ тут присутствует). Т.е., если оба argX целые, то и временная переменная временная, иначе вещественные. А дальше и стека она поступает или на операцию присвоения (где учитывается тип временной и принимающей переменной, также как и в СИ только здесь с типами проще). И для выполнения некоторых операций, мне тоже перевод в целые, опять же из-за границ и т.д.  ;)

З.Ы.
Ладно, пойду перепишу класс с учетом всего здесь сказанного. Возьму типы qint32 и double для работы со всем этим, но на вопросы по ним прошу ответить в любом случае. Как перепишу, выложу новый вариант.  ;)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 01, 2011, 06:08
Код:
    if (d > std::numeric_limits<float>::max())
А как это должно работать?  :) Если d = nan, то проверка не сработает. И что то за число больше максимального?

Например, d = 2e208, тогда условие отреагирует, т.к. экспонента максимального нормализованного значения 38 (3.4e38). Также условие отреагирует, если d=+inf, но т.к. мы оперируем float и ориентируемся на границы, то получить +inf из-за превышения границы в самом d (который типа double) мы не можем, кроме случаев, когда у нас выходит +/-inf из-за некоторых мат. операций.  ;) Т.е. надо сюда проверку, на inf, но я что-то пока не надумал, что делать в этом случае.  :-\


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Март 01, 2011, 13:03
1) Флоты везде 4 (float) и 8 (double) байт и везде следуют IEEE.

2) qint32 определяется через typedef, т.е. это не новый тип, поэтому проблем нет

3)
Цитировать
if (d > std::numeric_limits<float>::max())
Это длинно и неудобно, Сделайте пределы static членами класса чтобы в случае чего подстроить, напр
Код
C++ (Qt)
struct NNumeric {
...
static void InitLimits( void ) { mFloatMax = std::numeric_limits<float>::max(); }
static float mFloatMax;
};
 


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 02, 2011, 05:44
3)
Цитировать
if (d > std::numeric_limits<float>::max())
Это длинно и неудобно, Сделайте пределы static членами класса чтобы в случае чего подстроить, напр
Код
C++ (Qt)
struct NNumeric {
...
static void InitLimits( void ) { mFloatMax = std::numeric_limits<float>::max(); }
static float mFloatMax;
};
 

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


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Igors от Март 02, 2011, 10:06
А зачем мне создавать лишний статик-член класса NNumeric, если можно обойтись несколькими статик функциями возвращающими границы (как InitLimits, только с возвращением)?
Можно и так, дело вкуса. Смысл один - не разбрасывать ту длинную простыню по всему тексту класса


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 02, 2011, 12:37
В общем переписал свой класс и в итоге получилось запрятывание чисел диапазона float и int32, типом double. Разве, что способ записи целых чисел изменился, но зато добился полного контроля диапазона, как для int32, так float. Код стал компактнее, плюс в той функции, что я ранее приводил сильно сократил кол-во строк до понятного кода, т.к. теперь контроль границ у меня происходит внутри класса в виде внутренних функций. А, пока места с NAN еще не сделал, также не стоит забывать про погрешности у double (происходит при експоненте 11 или -11 у числа, но int32 эта погрешность не затрагивает, как во float-е). +/-inf приравнивается к максимальным границам, для моей ТЗ этого достаточно.

Собственно, код класса NNumeric:

Код:
#include <limits>
#include <cmath>

class NNumeric{
public:
    inline NNumeric(double Value = 0) {this->data._isreal = true; this->setValue(Value); this->data._id = -1; this->data._constvalue = false; this->data._name = "";}
    inline NNumeric(const NNumeric& a) {this->setConstValue(a.data._constvalue); this->setID(a.data._id); this->setName(a.data._name); this->setIsReal(a.data._isreal); this->setValue(a.data._value);}

    inline double round() {return floor(this->Value() + 0.5);}
    inline double fractional() {return this->Value() < 0.0 ? this->Value() - ceil(this->Value()) : this->Value() - floor(this->Value());}
    inline double integer() {return this->Value() < 0.0 ? ceil(this->Value()) : floor(this->Value());}

    inline int ID() {return this->data._id;}
    inline void setID(int ID) {this->data._id = ID;}
    inline bool ConstValue() {return this->data._constvalue;}
    inline void setConstValue(bool ConstValue) {this->data._constvalue = ConstValue;}
    inline QString Name() {return this->data._name;}
    inline void setName(QString Name) {this->data._name = Name;}
    inline void setIsReal(bool IsReal) {this->data._isreal = IsReal;}
    //Если ложно, то значение принимается и выдается (при печати числа на экране)
    // с отсечение дробной части числа и соблюдением границ типа qint32.
    inline bool IsReal() const {return this->data._isreal;}
    inline void setValue(double Value) {if (!this->IsReal()) Value = this->CheckingInt32Borders(Value); else Value = this->CheckingFloatBorders(Value); this->data._value = Value;}
    inline double Value() const {return double(this->CheckingFloatBorders(this->data._value));}

    inline void operator=(const NNumeric& a) {this->setConstValue(a.data._constvalue); this->setID(a.data._id); this->setName(a.data._name); this->setIsReal(a.data._isreal); this->setValue(a.data._value);}
    inline operator QString() {return QString::number(this->data._value);}
    inline operator double() {return double(this->CheckingFloatBorders(this->Value()));}
    inline operator float() {return this->CheckingFloatBorders(this->Value());}
    inline operator qint32() {return this->CheckingInt32Borders(this->data._value);}
    inline void operator++(int) {if (!this->IsReal()) this->data._value = this->CheckingInt32Borders(this->data._value+1); else this->data._value = this->CheckingFloatBorders(this->data._value+1);}
    inline void operator--(int) {if (!this->IsReal()) this->data._value = this->CheckingInt32Borders(this->data._value-1); else this->data._value = this->CheckingFloatBorders(this->data._value-1);}
    inline bool operator==(const NNumeric& a) {return this->data._value == a.Value();}
    inline bool operator!=(const NNumeric& a) {return this->data._value != a.Value();}
    inline bool operator&&(const NNumeric& a) {return this->data._value && a.Value();}
    inline bool operator||(const NNumeric& a) {return this->data._value || a.Value();}
    inline bool operator<(const NNumeric& a) {return this->data._value < a.Value();}
    inline bool operator<=(const NNumeric& a) {return this->data._value <= a.Value();}
    inline bool operator>(const NNumeric& a) {return this->data._value > a.Value();}
    inline bool operator>=(const NNumeric& a) {return this->data._value >= a.Value();}

private:

    inline long double Int32Max() const {return std::numeric_limits<qint32>::max();}
    inline long double Int32Min() const {return std::numeric_limits<qint32>::min();}
    inline long double FloatMax() const {return std::numeric_limits<float>::max();}
    inline long double FloatMin() const {return std::numeric_limits<float>::min();}
    inline float CheckingFloatBorders (double Value) const
    {
        //В этой функции +/-inf приравниваются к max и min границам.
        double d = fabs(Value);
        bool usign = Value >= 0.0;

        if (d >= this->FloatMax())
            return (usign) ? this->FloatMax() : -this->FloatMax();

        if (d <= this->FloatMin() && d > 0.0)
            return (usign) ? this->FloatMin() : -this->FloatMin();

        return float(Value);
    }
    inline qint32 CheckingInt32Borders(double Value) const
    {
        if (Value >= Int32Max()) return Int32Max();

        if (Value <= Int32Min()) return Int32Min();

        return qint32(Value);
    }


    struct NData {
    //если идентификатор равен -1, значит это либо временная переменная, либо константное значение
    qint32 _id;
    //константные или временные значения имен не имеют, только переменные.
    QString _name;
    double _value;
    bool _isreal;
    //это указывает на константное значение
    bool _constvalue;
    };
    NData data;
};

Пример суммы для целых и вещественных чисел в классе NNumeric:
Код:
            if (!arg1.IsReal() && !arg2.IsReal())
            {//здесь оба целые
                 arg0.setIsReal(false);
                 arg0.setValue(double(arg1) + double(arg2));
            }
            else
                arg0 = double(arg1) + double(arg2);


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 02, 2011, 14:44
Посмотрел ситуацию с nan и +/-inf. Все это ситуации с делением на ноль и мне негде их конролировать внутри класса, т.к. у него нет перегруженного оператора "/", а также целочисленного деления и остатка от него (это производится в той самой функции с мат. операциями там же). Погрешности в double - это уже отдельная тема. В итоге считаю свой вопрос уж точно раскрытым именно с моей постановкой задачи для класса NNumeric.

З.Ы.
Добился таки желаемого.  :D


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: brankovic от Март 02, 2011, 15:44
Посмотрел ситуацию с nan и +/-inf. Все это ситуации с делением на ноль и мне негде их конролировать внутри класса

переполнение в double тоже даст inf, а операции с inf могут дать nan


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 03, 2011, 05:10
Посмотрел ситуацию с nan и +/-inf. Все это ситуации с делением на ноль и мне негде их конролировать внутри класса

переполнение в double тоже даст inf, а операции с inf могут дать nan
Моей задачке это очень просто лечится. Границы, как видить соблюдаются, а Nan в число не запишешь, кроме умышленных способов, которые у меня не осуществимы. Т.е. на этапе записи числа, ситуация с +/-inf и nan исключена, а перед выполнением математической операции "/", где собственно и возникает nan при делении на ноль (больше я ситуаций не находил), достаточно проверить чтобы аргумент, на который делят не быль равен нулю, иначе вызываем особую ситуацию. В итоге, ситуации с переполнением у меня отпадают из-за проверки границ, а не допускание nan производится проверкой до выполнения мат. операции.  ;)


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: brankovic от Март 03, 2011, 08:39
а Nan в число не запишешь, кроме умышленных способов, которые у меня не осуществимы. Т.е. на этапе записи числа, ситуация с +/-inf и nan исключена
...
 В итоге, ситуации с переполнением у меня отпадают из-за проверки границ, а не допускание nan производится проверкой до выполнения мат. операции.  ;)

да, если * и / нет, а +- проверяются, то не получится inf

ps: в конструкторе ещё нет проверки входящего double на inf/nan.

на правах офтопа (code review :)):
1. все операторы и функции, которые не изменяют объект (операторы == != > < и т.д.) лучше делать const, иначе они не будут работать в некоторых случаях (в std::map, например).
2. явный конструктор копирования, как и operator= в этом классе не нужны (определенные по умолчанию здесь вполне подойдут)
3. inline в теле класса писать не обязательно, если метод определён в классе, то inline  подразумевается.


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: Eten от Март 03, 2011, 09:18
а Nan в число не запишешь, кроме умышленных способов, которые у меня не осуществимы. Т.е. на этапе записи числа, ситуация с +/-inf и nan исключена
...
 В итоге, ситуации с переполнением у меня отпадают из-за проверки границ, а не допускание nan производится проверкой до выполнения мат. операции.  ;)

да, если * и / нет, а +- проверяются, то не получится inf

ps: в конструкторе ещё нет проверки входящего double на inf/nan.

на правах офтопа (code review :)):
1. все операторы и функции, которые не изменяют объект (операторы == != > < и т.д.) лучше делать const, иначе они не будут работать в некоторых случаях (в std::map, например).
2. явный конструктор копирования, как и operator= в этом классе не нужны (определенные по умолчанию здесь вполне подойдут)
3. inline в теле класса писать не обязательно, если метод определён в классе, то inline  подразумевается.
1) про const учту, хотя по началу думал, что так делать излишне.
2) а как сделан конструктор копирования и operator= по умолчанию, через inline или без него?
3) если я правильно понял, объявленная и определенная функция в классе, то inline будет проставлен уже без моей помощи?


Название: Re: Как правильно сделать преобразование для целых и вещественных чисел?
Отправлено: brankovic от Март 03, 2011, 11:03
2) а как сделан конструктор копирования и operator= по умолчанию, через inline или без него?
3) если я правильно понял, объявленная и определенная функция в классе, то inline будет проставлен уже без моей помощи?

2) работает как если бы был inline
3) да

вообще inline лучше не злоупотреблять, поскольку он обычно не нужен. Например gcc на -O3 спокойно инлайнит не-инлайн функции.