Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > как получить целую часть веществ. числа


Автор: Frekenbok 6.1.2007, 14:35
Привет всем!
Вот проблемка возникла на пустом месте... Помогите, пожалуйста.
Есть у меня переменная  num типа double. Мне нужно получить целую часть числа или округлить "вниз". Вроде бы для этого существует функция floor, что меня очень радовало. Но на деле оказалось, что если num=1.0, то floor(num)=0.0, а мне нужно 1.0! 
Был у меня и другой вариант: извлечь целую и дробную части fract=modf(num, &intnum), но в этом случае если num=1.0, то получается fract=1.0, а intnum=0.0. Почему так?
Если просто выводить printf("%.0lf",num), то он сам округляет, и получается не то, что нужно.
На всякий случай поясню, как получаю num, может, это имеет значение, и где-то происходят какие-то округления и обрезания (в С ведь вечно так  smile ):
Код

double grad=0.0, radn=0.0, num=0.0, min=0.0,ho;
scanf("%lf",&grad);
   radn=M_PI/180.0*grad;
   num=radn*6.0/M_PI;

Для grad ввожу значения 30, 45 и др.
Нужно, чтобы для num=1.0 (1.1, 1.2,...,1.8,1.9) на экран вывелось 1, для num=2.0 (2.1, 2.2,...,2.8,2.9) вывелось 2 и т.п. (надеюсь, понятно  smile )
P.S.Задачка на самом деле про то, как по углу часовой стрелки определить, сколько часов и минут на часах.

Автор: jonie 6.1.2007, 14:40
привести типы просто может?  smile должно работать... ну с руганью компиляторов некоторых....
Код

double f=1.0;
int n = f;

ЗЫ: не проверял - под рукой компилятора нет. Но сколь память не изменяет...

Автор: WERITAS 6.1.2007, 14:42
А другие функции округления не пробовали? round например?

Автор: ILAgent 6.1.2007, 15:03
Так можно, наверное:
Код

num=( (num-floor(num))==1 )?num:floor(num);

Автор: nickless 6.1.2007, 16:31
Цитата(Frekenbok @ 6.1.2007,  13:35)
Вроде бы для этого существует функция floor, что меня очень радовало. Но на деле оказалось, что если num=1.0, то floor(num)=0.0, а мне нужно 1.0! 

floor() работает правильно, проблема в том, что ввиду двоичного представления чисел в компьютере num не может быть равен точно 1.0, только что-то вроде 0.9999999999954 (твой случай) или 1.000000000121, аналогично для modf()
В принципе это не должно сильно мешать, но если в результате будут заметные ошибки, попробуй отлавливать такие места примерно так: 
Код
double eps = 0.000001;
if ((num > 1.0 - eps) && (num < 1.0 + eps))
{
    ...
}

Автор: Dov 6.1.2007, 17:10
Код

...
int num;
...
num = int(radn*6.0/M_PI);
...
printf("%d", num);

Автор: Frekenbok 7.1.2007, 12:34
Цитата(WERITAS @  6.1.2007,  14:42 Найти цитируемый пост)
А другие функции округления не пробовали? round например? 
В том то и дело, что в моем древнем Борланде такой функции еще нет, поэтому и весь сыр-бор...  smile 

Цитата(jonie @  6.1.2007,  14:40 Найти цитируемый пост)
double f=1.0;
int n = f;
Так не получается.

Цитата(Dov @  6.1.2007,  17:10 Найти цитируемый пост)
Код

num = int(radn*6.0/M_PI);
И так тоже.

Цитата(nickless @  6.1.2007,  16:31 Найти цитируемый пост)
ввиду двоичного представления чисел в компьютере num не может быть равен точно 1.0, только что-то вроде 0.9999999999954 (твой случай) или 1.000000000121
Вот это в самую точку. Были у меня подозрения, но в окошке Watch мой num так убедительно был равен 1.0! Да, не стоит доверять глазам своим.
Только если делать так:
Цитата(nickless @  6.1.2007,  16:31 Найти цитируемый пост)
if ((num > 1.0 - eps) && (num < 1.0 + eps))
то вместо 1.0 мне придется подставлять как раз целую часть числа num, ведь у меня num может быть равен и 2.0, и 3.0 и т.д. И началась песня сначала... А так вообще работает, спасибо.
Но это меня натолкнуло на такой вариант решения: округлить мой num до 0.000001. А для этого использую чью-то чужую умную мысль. Получилось примерно вот что:
Код

double eps=0.000001;
num=floor(num/eps+0.5)*eps; // округляем до 0.000001
intnum=(int)num; // теперь берем целую часть

Вот хорошо бы такие вещи просматривать в дебаггере типа ассемблерного, только как это сделать? Я подозреваю, что надо воспользоваться дизассемблером и там просмотреть ЕХЕ-шник. Вот только где взять этот дизассемблер и какой лучше? Посоветуйте что-нибудь. Или можно как-нибудь еще просмотреть ячейки памяти со значением num (причем "до" и "после", пошагово)?

Автор: Dov 7.1.2007, 23:47
Цитата(Frekenbok @  7.1.2007,  11:34 Найти цитируемый пост)
Так не получается.

Цитата(Frekenbok @  7.1.2007,  11:34 Найти цитируемый пост)
И так тоже.

Что у тебя не получается, сказать можешь или это секрет.  smile 

Автор: Frekenbok 8.1.2007, 09:48
Dov, я понимаю, что выглядит все логично, взял int-ом обрезал и все. Но на практике не всегда действует. У меня Borland Turbo C++ 3.0. При значении num==1.0 (int)num получается равен 0. А все потому, что я не просто присваиваю num единицу, эта единица у меня получается в результате вычислений (см. первое сообщение), т.е. цитирую еще раз:

Цитата(nickless @  6.1.2007,  16:31 Найти цитируемый пост)
ввиду двоичного представления чисел в компьютере num не может быть равен точно 1.0, только что-то вроде 0.9999999999954 (твой случай) или 1.000000000121

При использовании (int)num результат получается неверным, т.е. не тем, что мне надо.

Автор: W4FhLF 8.1.2007, 12:25
Borland Turbo C++ 3.0 поддерживает inline asm вставки? Тогда сделай что-то вроде этого:

Код

float f;
unsigned int x;
__asm
{
fld f
fistp x
};

Автор: nickless 8.1.2007, 18:26
W4FhLF, какая разница, это не от компилятора зависит, а от процессора

Автор: Daevaorn 8.1.2007, 18:28
Цитата(nickless @  8.1.2007,  19:26 Найти цитируемый пост)
какая разница, это не от компилятора зависит, а от процессора 

от процессора зависит, поддерживает ли компилятор inline assembler?smile

Автор: nickless 8.1.2007, 18:32
Цитата(Daevaorn @ 8.1.2007,  17:28)
от процессора зависит, поддерживает ли компилятор inline assembler?smile

Нет, тут весь топик идет речь о том, что floor(f) может быть равен 0.0, даже если f==1.0

Автор: W4FhLF 8.1.2007, 18:52
Цитата(nickless @  8.1.2007,  18:26 Найти цитируемый пост)
W4FhLF, какая разница, это не от компилятора зависит, а от процессора


Ты бы для начала постарался понять, что такое вещественное число в памяти(в этом тебе поможет моя статья: http://hunger.ru/articles/num), а потом посмотрел бы, что значит команда fist, но я не буду томить и скажу тебе, что эта команда округляет в большую сторону вещественное значение и записывает его в память.

Автор: nickless 8.1.2007, 19:08
Цитата(W4FhLF @ 8.1.2007,  17:52)
Ты бы для начала постарался понять, что такое вещественное число в памяти(в этом тебе поможет моя статья: http://hunger.ru/articles/num), а потом посмотрел бы, что значит команда fist

Спасибо, я знаю.
Между прочим fist округляет не в большую сторону, а в зависимости от fps status word, но и это не поможет округлить 0.9999999999 _вниз_ (человеку надо округлять в меньшую сторону) и получить 1

Автор: Ln78 8.1.2007, 19:32
Мне кажется, в данной задаче суть в ошибках округления. Чтобы их уменьшить, можно воспользоваться тем, что в часах для угловой стрелки 360 угловых градусов соответствуют 12 временным часам или 720 временным минутам. Поэтому можно использовать такой вариант:
Код

    int SumMin=int(2*grad);
    int Ho=SumMin/60, Min=SumMin%60;

Автор: W4FhLF 8.1.2007, 19:41
Цитата(nickless @  8.1.2007,  19:08 Найти цитируемый пост)
Между прочим fist округляет не в большую сторону, а в зависимости от fps status word, но и это не поможет округлить 0.9999999999 _вниз_ (человеку надо округлять в меньшую сторону) и получить 1


Да, уточнил, я думал на флаг RC ориентируется только fistp. А как это округлить 0.9999999 в меньшую сторону и получить при этом 1? Что-то мне неясно. 

Кстати, вот пример округления до ближайшего целого, это поможет избавиться от погрешности с которой сопроцессор представляет числа в памяти:

Код

#include <stdio.h>

int main()
{
    float f;
    unsigned int x;
    unsigned short int y;
    f = 0.999999;
    __asm {
        and y,0xFFFF
        fstcw word ptr[y]
        and word ptr[y],0xFFCF
        fldcw word ptr[y]
        fld f
        fistp dword ptr[x]
    };
    return 0;
}


На выходе в x имеем 1. 



Автор: Mayk 8.1.2007, 20:15
1)  можно тупо изобрести велосипед
Код

double round(double x)
{
  double fl = floor(x);
  double cl = ceil(x);
  if( (cl - x) < (x - fl) ) { // если x ближе к cl, чем к floor
      return cl;
  } 
  return fl;
}


2) достать нормальный компилятор.

Автор: Anikmar 9.1.2007, 19:55
Можно так:

floor(num+0.1);

или

floor (num+0.01)

Автор: Frekenbok 10.1.2007, 04:13
Цитата(Anikmar @  9.1.2007,  19:55 Найти цитируемый пост)
Можно так:
floor(num+0.1);

А если число будет 1.954187? Тогда floor(num+0.1) будет равно 2. А надо 1.

Автор: codelord 10.1.2007, 07:03
Frekenbok, а вариант 
Mayk, чем не устраивает?

Автор: Frekenbok 10.1.2007, 07:34
Цитата(codelord @  10.1.2007,  07:03 Найти цитируемый пост)
а вариант 
Mayk, чем не устраивает? 

Все-таки в 1 случае из 25 вывел неверно  smile 
Можно ли просмотреть значения переменных С++ в дебаггере наподобие ассемблера? чтобы видно было, какое число получается в ячейке памяти?

W4FhLF, кстати, твой вариант не сработал, надо как-то подгонять под мои данные, наверное. У меня double num вместо float f. Не знаю, имеет ли это значение, наверное, да. До программирования сопроцессора еще не дошло дело. Пока только процессор.

Добавлено @ 07:36 
А почему, кстати, в моем варианте число именно меньше 1 (0.99999999), а не больше (1.000000000024)? В скобках я к примеру указываю.  smile 

Автор: W4FhLF 10.1.2007, 08:10
Цитата(Frekenbok @  10.1.2007,  07:34 Найти цитируемый пост)
W4FhLF, кстати, твой вариант не сработал, надо как-то подгонять под мои данные, наверное. У меня double num вместо float f. Не знаю, имеет ли это значение, наверное, да. До программирования сопроцессора еще не дошло дело. Пока только процессор.



Ну имеет конечно, double занимает 8 байт, а float 4. Так вот попробуй:

Код

int main()
{
    
    double f; // 8 байт.
    unsigned long int x; // 8 байт.
    unsigned short int y; // 2 байта для регистра сопроцессора CWR.
    f = 1.954187;
    __asm {
        fstcw word ptr[y] // Сохранение регистра в память.
        xor word ptr[y],0x400 // Устанавливаем десятый бит. Флаг RC будет равен 01, это говорит сопроцессору, что при сохранении 
                  //нужно округлять числа в меньшую сторону
        fldcw word ptr[y] // Загружаем регистр снова
        fld f // Кладём значение f на вершину стека сопроцессора.
        fistp qword ptr[x] // Сохраняем в переменную x округлённое до меньшего целого значение с вершины стека.
    };
    return 0;
}


На выходе в x получаем 1. Что и требовалось доказатьsmile

Добавлено @ 08:11 
Цитата(Frekenbok @  10.1.2007,  07:34 Найти цитируемый пост)
А почему, кстати, в моем варианте число именно меньше 1 (0.99999999), а не больше (1.000000000024)? В скобках я к примеру указываю.


А оно почти всегда будет чуть меньше, кроме тех случаев, когда ты сохраняешь периодичную, с точки зрения двоичной системы, дробь(0.125 или 8.625 например). Это погрешность при переводе десятичных дробей в двоичные. 

Автор: codelord 10.1.2007, 08:13
Цитата

Нужно, чтобы для num=1.0 (1.1, 1.2,...,1.8,1.9) на экран вывелось 1, для num=2.0 (2.1, 2.2,...,2.8,2.9) вывелось 2 и т.п.

ну вот от меня еще вроде работает, надеюсь и у тебя тоже будет.
Код

long double round2(long double x)
{
 long double fl = floor(x);
 long double cl = ceil(x);
  if((cl - x)  <0.0999 ) { // погрешность выставлял на 0.1
      return cl;
  } 
  return fl;
}


Добавлено @ 08:21 
Цитата

Можно ли просмотреть значения переменных С++ в дебаггере наподобие ассемблера? чтобы видно было, какое число получается в ячейке памяти?

наведи каретку на переменную и ctrl + f5 по моему вроде так smile

Автор: Frekenbok 16.1.2007, 11:02
Цитата(W4FhLF @  10.1.2007,  08:10 Найти цитируемый пост)
А оно почти всегда будет чуть меньше, кроме тех случаев, когда ты сохраняешь периодичную, с точки зрения двоичной системы, дробь(0.125 или 8.625 например). Это погрешность при переводе десятичных дробей в двоичные. 

Тогда получается, что если у меня число 0.999999, то мне, наоборот, надо округлять "вверх"!  smile  Я-то думала, что если у меня в окне Watch num=1.0, то он будет ну не меньше 1. Получается, я всех ввела в заблуждение. Sorry.  Поэтому вариант W4FhLF с асм-вставкой работает правильно, но мне не подходит. 
А вот вариант codelord то, что надо. Спасибо.

Цитата(codelord @  10.1.2007,  08:13 Найти цитируемый пост)
наведи каретку на переменную и ctrl + f5
В Turbo C++ 3.0 не работает.
Вообще, всем спасибо за помощь. Думаю, тему можно закрыть  smile 


Автор: Frekenbok 16.1.2007, 11:26
Цитата(Ln78 @  8.1.2007,  19:32 Найти цитируемый пост)
Мне кажется, в данной задаче суть в ошибках округления. Чтобы их уменьшить, можно воспользоваться тем, что в часах для угловой стрелки 360 угловых градусов соответствуют 12 временным часам или 720 временным минутам. Поэтому можно использовать такой вариант:

код C++
1:
2:

    int SumMin=int(2*grad);
    int Ho=SumMin/60, Min=SumMin%60;


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

Автор: xKrIvEdKox 31.5.2009, 02:01
x:=round(x*100)/100; 
smile 

Автор: Dmi3ev 1.6.2009, 00:59
удалил...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)