LINUX.ORG.RU

Сомнения...

 


0

3

Решал одно из упражнений этой книги:
Само упражение
Решение:

#include <stdio.h>

#define FT 30.48
#define INCH 2.54

int main(void) {
	int ftInt;
	float sm;
	double ftDouble, inch;
	printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
	scanf("%f", &sm);
	while (sm > 0) {
		ftInt = sm / FT;
		ftDouble = sm / FT;
		inch = ((ftDouble - ftInt) * FT) / INCH;
		printf("%.1f см = %d футов, %.1f дюймов\n", sm, ftInt, inch);
		printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
		scanf("%f", &sm);
	}
	printf("Работа завершена.\n");
	return 0;
}
Собственно появились сомнения о правильности в принятии решения, но в голову ничего другого не приходит.

until сподручней будет.

anonymous
()

По крайней мере двойной сканф — это правильно. Насчет вывода printf'ом double'а через %f — не уверен.

crowbar
()

Собственно появились сомнения о правильности в принятии решения, но в голову ничего другого не приходит.

ИМХО тут только одна(принципиальная) ошибка: зачем тебе разные типы? Почему нельзя было всё сделать в double?

Вторая ошибка: «высота». Я вот не понимаю, диагональ экрана нельзя твоей программой вычислить? Можно. Тогда почему «высота»?

Третья ошибка: забыл задать sm при определении. Лучше ВСЕ переменные задавать, когда их объявляешь, это хорошая идея.

emulek
()
double sm = 1.0;
while(1){
  printf(…);
  scanf("%f", &sm);
  if(sm<=0)  break;
  printf(…);
}
emulek
()
Ответ на: комментарий от crowbar

Насчет вывода printf'ом double'а через %f — не уверен.

AFAIK аргументы в printf ВСЕГДА преобразуются в double. %f только определяет формат вывода

man 3 printf

f, F The double argument is rounded and converted to decimal notation in the style [-]ddd.ddd, where the number of digits after the decimal-point character is equal to the precision specification. If the precision is missing, it is taken as 6; if the precision is explicitly zero, no decimal-point character appears. If a decimal point appears, at least one digit appears before it.

emulek
()
Ответ на: комментарий от crowbar

Лучше break в бесконечном цикле?

да. Потому-что задача такая:

1. спросить пользователя число

2. если число ≤0, то выход

3. перевести и вывести в stdout

4. goto 1

В самой задаче заложен алгоритм, и не нужно его менять. Это ведёт к проблемам в будущем, т.к. исправлять printf/scanf нужно будет в двух местах, хотя в ТЗ только одно такое место.

emulek
()
Ответ на: комментарий от crowbar

можно так сделать:

double sm = 1.0;
do{
  printf(…);
  scanf("%f", &sm);
  if(sm>0)
  {
    printf(…);
  }
}while(sm>0);

тут лишняя проверка, но gcc скорее всего её выкинет с -O2.

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

Почему нельзя было всё сделать в double?

int main() {
        double sm = 1.0;
	scanf("%f", &sm);
	return 0;
}

:-$ make
gcc -c test.c
test.c: In function ‘main’:
test.c:17:2: warning: format ‘%f’ expects argument of type ‘float *’, but argument 2 has type ‘double *’ [-Wformat=]
  scanf("%f", &sm);
  ^
gcc test.o -o test

Вторая ошибка: «высота». Я вот не понимаю, диагональ экрана нельзя твоей программой вычислить? Можно. Тогда почему «высота»?

условие такое, ну...

Третья ошибка: забыл задать sm при определении

Даже если эту переменную scanf'ом потом задаю?

Silencer
() автор топика

Про недублирование scanf() уже написали.

double (технически) можно выводить через %f: при вызове varargs-функции все float-аргументы автокастятся в double, так что printf() не различает %lf и %f. А вот для scanf() важно различать %f и %lf, чтобы он не загадил память.

Использовать неявный каст int для получения целой части — не очень хорошая идея, так как у double гораздо больше диапазон представимых значений и целая часть может не влезть в int. Лучше использовать trunc(), чтобы получить целую часть в виде double-значения.

P.S. s/sm/cm/g, сантиметр = centimeter.

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

А вот для scanf() важно различать %f и %lf, чтобы он не загадил память.

А вот это в яблочко!

P.S. s/sm/cm/g, сантиметр = centimeter.

/me покраснел

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

test.c:17:2: warning: format ‘%f’ expects argument of type ‘float *’, but argument 2 has type ‘double *’ [-Wformat=] scanf(«%f», &sm);

настоящие хакеры маны не читают, да?

А ты прочитай, чай не в маздае:

l Indicates either that the conversion will be one of d, i, o, u, x, X, or n and the next pointer is a pointer to a long int or unsigned long int (rather than int), or that the conversion will be one of e, f, or g and the next pointer is a pointer to double (rather than float). Specifying two l characters is equivalent to L. If used with %c or %s, the corresponding parameter is considered as a pointer to a wide character or wide-character string respectively.

условие такое, ну...

жертва ЕГЭ детектед. Думать надо, когда программу пишешь.

Даже если эту переменную scanf'ом потом задаю?

этот scanf не нужен. Один scanf всяко лучше двух. Тебе же проще, если ты «%d» будешь менять на «%ld».

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

emulek
()
Ответ на: комментарий от Silencer

дык double оно 64 бита, а float всего 32. Потому когда ты выводишь float/double, его можно всегда скастовать в более большой тип. А вот когда вводишь, тут по другому, большой тип в маленький не засунуть, вот и приходится делать два варианта аргументов, отдельно для double и отдельно для float.

emulek
()

inch = ((ftDouble - ftInt) * FT) / INCH;

да, тут есть грабли, про которые часто забывают: порядок действий между точками следования НЕ определён. Конечно есть ассоциативность и приоритет, но в этом случае приоритет(умножения и деления) одинаковый, а ассоциативность не помешает компилятору изменить порядок действий. Дело в том, что компилятор полагает, что (A*B)*C==A*(B*C). Но это действительно так только тогда, когда тип A, B и C одинаковый. Если тип разный, то возможны варианты. 1/3*3.0→(1/3)*3.0→0. Что не мешает компилятору посчитать 1/3*3.0→1*⅓*3.0→1*(⅓*3.0)→1.0 (это возможно потому, что компилятор любит переводить целочисленное деление на константу в целочисленное умножение на обратную константу, в частности ⅓ это 0x55555555/0x100000000).

Таким образом, результат может быть ОЧЕНЬ далёким от ожидаемого.

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

тут лишняя проверка, но gcc скорее всего её выкинет с -O2.

Результат проверки можно сохранить в переменную с говорящим именем. Заодно читабельность повысится.

Также вычисление футов/дюймов какое-то неочевидное. Футы - это результат целочисленного деления сантиметров на 30.48. Дюймы - это остаток от деления, поделенный еще раз на 2.54. Подрихтовал имена переменных, получилось нечто вроде этого (C99):

#include <stdio.h>
#include <stdbool.h>
#include <math.h>

#define CENTIMETERS_PER_FEET 30.48
#define CENTIMETERS_PER_INCH 2.54

int main() {
    bool quit = false;
    while (!quit) {
        double centimeters = 0;
        printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
        scanf("%lf", &centimeters);
        quit = centimeters <= 0;
        if (!quit) {
            int feets = floor(centimeters / CENTIMETERS_PER_FEET);
            double inches = fmod(centimeters, CENTIMETERS_PER_FEET) / CENTIMETERS_PER_INCH;
            printf("%.1f см = %d футов, %.1f дюймов\n", centimeters, feets, inches);
        }
    }
    printf("Работа завершена.\n");
    return 0;
}
archie
()
Ответ на: комментарий от anonymous

да, ошибся. В чём проблема? Прочитай ман, и исправь.

А я scanf(3) в последний раз лет 7 назад юзал, уже и не помню деталей.

emulek
()
Ответ на: комментарий от archie

Результат проверки можно сохранить в переменную с говорящим именем. Заодно читабельность повысится.

согласен. Конечно для такой мелкой программы это не важно, но в общем случае ты прав.

Также вычисление футов/дюймов какое-то неочевидное. Футы - это результат целочисленного деления сантиметров на 30.48. Дюймы - это остаток от деления, поделенный еще раз на 2.54.

тоже верно. Об этом я как-то не подумал. Это как разница во времени размером 12345 секунд. Правильно 3 часа, 25 минут, и 45 секунд.

int feets = floor(centimeters / CENTIMETERS_PER_FEET);

я-бы остался в doble, как-то так:

double feets = centimeters / CENTIMETERS_PER_FEET;
printf("%.0f", feets);

т.к. числа положительные, то должно работать правильно.

(там ведь проверка на <=0 есть)

emulek
()
Ответ на: комментарий от archie

переменная quit это тяжёлое наследие Бёма—Якопини, любой из уже предложенных вариантов(даже авторский - который совсем не идеал) лучше чем использование булевой тут.

qulinxao ★★☆
()

switch(1){
        while(sm>0){
		ftInt = sm / FT;
		ftDouble = sm / FT;
		inch = ((ftDouble - ftInt) * FT) / INCH;
		printf("%.1f см = %d футов, %.1f дюймов\n", sm, ftInt, inch);
		case 1:printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
	        scanf("%f", &sm);
        }
}

;) //переход в низ по коду менее опасен чем обратно, ну и следить что -бы в месте куда_пришли всё должное было инициализированно :)

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

симметричней:

switch(1){do{
		ftInt = sm / FT;
		ftDouble = sm / FT;
		inch = ((ftDouble - ftInt) * FT) / INCH;
		printf("%.1f см = %d футов, %.1f дюймов\n", sm, ftInt, inch);
	 case 1:printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
	        scanf("%f", &sm);
} while(sm>0)}

что то лиспо-питонство вспоминается.

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

я-бы остался в doble, как-то так:

Да, ты прав, тут пожалуй лучше все в даблах считать. Но я бы все равно отбрасывал дробную часть у футов. Например 12345 секунд ты скорее всего захочешь переводить в 3 часа, 25 минут и 45 секунд, а не в 3.429166667 часа, 25.75 минут и 45 секунд.

archie
()
Ответ на: комментарий от qulinxao

переменная quit это тяжёлое наследие Бёма—Якопини

Ты так говоришь, как будто это что-то плохое. Си - это типичный структурный ЯП. Не вижу причин не использовать в нем основные положения структурного программирования.

любой из уже предложенных вариантов(даже авторский - который совсем не идеал) лучше чем использование булевой тут.

Спорно. Авторский вариант содержит копипаст printf/scanf, а копипаст - это зло, сильно хуже булевой переменной.

Более-менее был вариант с break, но в общем случае обильное использование бряков и ретурнов создает множественные точки выхода из блоков, что запутывает логику и приводит к багам. Особенно если в блоках выделяются ресурсы, а потом нужно не забывать их освобождать перед каждым бряком и ретурном. В C++ с RAII это не проблема, но делать так в Си я бы не стал.

archie
()

конечно не правильно - язык Си не подходит для подобного рода задач

anonymous
()

если см.немного, и я c нулями не ошибся :)

#define FT(cm) (((int)(cm*10000))/304799)
#define INCH(cm) (((int)(cm*10000))%304799)/25400.0
double cm=0.0; // высота в см.
while(1) {
 printf("введите высоту в см.%s:",(cm>0?"(<=0 для выхода)":""));
 if (scanf("%g",&cm)==1 && cm>=0.0) {
    printf("%.2g cm= %d футов = %.2g дюьмов\n",cm,FT(cm),INCH(cm));
 } else break;
};

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

} else break;

моришь

та ды уж инвертировать условие и без вложенности.(обычный выход из середины)

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

переменная quit это тяжёлое наследие Бёма—Якопини, любой из уже предложенных вариантов(даже авторский - который совсем не идеал) лучше чем использование булевой тут.

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

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

ты талант в «антипрофилировании» оппонента.

сам подход «избавимься от всякого перехода(в том числе break,continue,return „не поместу“) добавив достаточное число флажков и если чё копипаст -это профанация .

лучше комменть switch(переменая){do{.... ; default:....}while(cm>0)}

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

Например 12345 секунд ты скорее всего захочешь переводить в 3 часа, 25 минут и 45 секунд, а не в 3.429166667 часа, 25.75 минут и 45 секунд.

ну тут я не знаю. Зачем? Всё равно можно _выводить_ только целую часть футов.

Разве-что нужно предусмотреть случай(для времени), когда получается 10.9999 часов.

emulek
()
Ответ на: комментарий от archie

а потом нужно не забывать их освобождать перед каждым бряком и ретурном. В C++ с RAII это не проблема, но делать так в Си я бы не стал.

тут нет никакого выделения.

Если-бы выделение было, я-бы его вынес за скобки. Следить за изменением псевдофайла в /proc (комментарий) тут куча continue/break, но это не мешает освобождать память.

emulek
()
Ответ на: комментарий от MKuznetsov

если см.немного, и я c нулями не ошибся

Это даже не говнокод, это просто говно.

emulek
()
Ответ на: комментарий от qulinxao

сам подход «избавимься от всякого перехода(в том числе break,continue,return „не поместу“) добавив достаточное число флажков и если чё копипаст -это профанация .

ты наверное не понял: тут флажок нужен как раз ВМЕСТО копипасты. И ВМЕСТО того, что-бы ломать ожидаемую структуру цикла, который иначе ВНЕЗАПНО вываливается посередине. В этом примере конечно это не важно, но для сложного цикла поиск ошибки может стать адом, когда надо найти глубоко закопанный break.

На самом деле, это всё просто для удобочитаемости, машинный код обычно получается одинаковый. Просто такой вариант (с bool quit) проще всего читать. Поверь, с опытом кодинга на C/C++ ты это поймёшь.

лучше комменть switch

что значит «лучше»? Какие грибы надо для этого глотать?

emulek
()
Ответ на: комментарий от qulinxao

вопрос «что» из твоего «длиннее» полный список возможно?

можно как-нибудь подробнее вопросы задавать? А то я по скудоумию тебя не понимаю.

emulek
()
  • Что будет если я введу в качестве первого значения «qwe»?
  • Что произойдёт, если я введу qwe в качестве второго значения?
  • Что произойдёт, если программа наткнётся на EOF?

У тебя получилась плохая негодная программа, которая постоянно зависает. Вставь, хотя бы, сравнение результата работы scanf с нулём.

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

В общем если ты просто вынесешь запрос строки в отдельную процедуру, то ты легко избавишься от дублирования кода.

#include <stdio.h>

#define FT 30.48
#define INCH 2.54

float request_input() {
  float sm = 0;
  printf("Введите высоту в сантиметрах (<=0 для выхода из программы): ");
  scanf("%f", &sm);
  return sm; // Если scanf завершился неудачно, то sm не изменится и мы вернём 0.
}

int main(void) {
  int ftInt;
  float sm;
  double ftDouble, inch;
  while ((sm = request_input()) > 0) {
    ftInt = sm / FT;
    ftDouble = sm / FT;
    inch = ((ftDouble - ftInt) * FT) / INCH;
    printf("%.1f см = %d футов, %.1f дюймов\n", sm, ftInt, inch);
  }
  printf("Работа завершена.\n");
  return 0;
}
kim-roader ★★
()
Последнее исправление: kim-roader (всего исправлений: 1)
Ответ на: комментарий от kim-roader
$ ./test
Введите высоту в сантиметрах (<=0 для выхода из программы): 1e30
1000000015047466219876688855040.0 см = -2147483648 футов, 393700793325774070176750239744.0 дюймов

Сишечка она такая сишечка.

anonymous
()
Ответ на: комментарий от kim-roader

Никто не застрахован от переполнения типов данных

тут кто то предложил программно обрабатывать(как возможные не штатные ситуации) не числа в качестве входных данных, а аноним даже упростил - ввели годное число сотых метра и «почему-то» получили не годное число футов

а ведь последнее более опасно чем первое

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

Сишечка она такая сишечка.

я говорил ТСу, что надо всё делать в double. Но ему нравятся грабли.

emulek
()
Ответ на: комментарий от qulinxao

тут кто то предложил программно обрабатывать(как возможные не штатные ситуации) не числа в качестве входных данных

вроде-бы они и так обрабатываются, ЕМНИП scanf(3) выдаёт 0, если NaN.

У ТСа просто копипасты куча, и не везде он проверяет. Именно потому, копипаста == зло.

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

У ТСа просто копипасты куча, и не везде он проверяет. Именно потому, копипаста == зло.

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

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

ЕМНИП scanf(3) выдаёт 0, если NaN

scanf если на входе видит строку, которая не соответствует шаблону, — оставляет эту строку в потоке, не трогает свои аргументы и возвращает 0 (если искался всего один шаблон) в качестве результата. Таким образом ввод некорректного числа (например кто-то случайно нажал «]» перед переводом строки) закончится в оригинальном коде вечным циклом.

kim-roader ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.