язык C в Linux: вопросы начального уровня

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 15:15

Olej писал(а):
Виктория писал(а): Мне не совсем понятно назначение тестов в преподавании программирования. Почему бы не использовать лишь задачи на "придумать алгоритм и реализовать его в программе"?
А последствий из этого простейшего примера можно сделать немало:
Смотрите каких интересных (как мне кажется) примеров я вам подкину на "придумать алгоритм и реализовать его в программе" ;-) :

Итерационные вычисления, приближения ... Это не только часть вычислительной математики, но и, временами, составная часть программ обрабатывающих экспериментальные данные ... или внешние данные в АСУТП + итерационная подгонка параметров модели процесса под полученные данные. Иногда это происходит в реальном времени, многократно и часто ;-) .

В примитивном виде итерационное приближение записывают так:

Код: Выделить всё

while( <что-то> < 1.E-7 ) {
   ... // продолжпем
}

Где вот то <что-то> - это "невязка", расхождение ... а сравнивается оно с малым значением epsilon (1.E-7), берущимся "с потолка".
Гораздо лучше при старте программы вычислить оценку этого epsilon для нашей конкретной платформы (процессор, ОС, компилятор, библиотеки, ...).
Что-то типа:

Код: Выделить всё

double epsilon = 1.;
while( 1 ) {
   if( 1. + epsilon == 1. + epsilon / 10. ) break;
   epsilon /= 10.;
}
Т.е. когда мы уменьшим epsilon настолько, что уже добавление такого epsilon к 1 будет неотличимо от такого же значения для ещё меньшего epsilon ...
И далее используем epsilon увеличенное на 1-2 итерации назад (для страховки)...
Об этом много написано такими классиками математических численных вычислений как Дж.Форсайт, М.Малькольм, К.Моулер "Машинные методы математических вычислений.".
Изображение

Вот предлагаю такой тест:

Код: Выделить всё

#define DIV 10.

void test021( void ) {    // максимальная точность итерационных вычислений
   printf( "максимальная точность итерационных вычислений:\n" );
   int i;
   float x = 1.;
   double y = 1.;
   for( i = 0; ; i++ ) {
      if( (float)1. + x == (float)1. + x / (float)DIV ) break;
      x /= (float)DIV;
   }
   printf( "для float\t: %e (число итераций %d)\n", x, i );
   for( i = 0; ; i++ ) {
      if( ( (double)1. + y ) == ( (double)1. + y / (double)DIV ) ) break;
      y /= (double)DIV;
   }
   printf( "для double\t: %e (число итераций %d)\n", x, i );
}
И вот результат ... он не так чтобы совсем тривиальный и ожидаемый (для меня, по крайней мере, кажется):

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ ./type 2
02 ---------------------------------------
максимальная точность итерационных вычислений:
для float: 1.000000e-20 (число итераций 20)
для double: 1.000000e-20 (число итераций 20)
------------------------------------------
Вас не смущает, что независимо от точности представления (float или double) итоговая максимально достижимая точность (относительная) итерационных вычислений остаётся неизменной?

P.S. Прилагаемый файл 1.c - это из состава архива examples.tgz, но так ещё этого теста не было ... но не архивировать же всё по малейшему чиху? ;-)
Вложения
1.c
(3.43 КБ) 258 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 15:32

Olej писал(а): Вас не смущает, что независимо от точности представления (float или double) итоговая максимально достижимая точность (относительная) итерационных вычислений остаётся неизменной?
Но если бы это было всё! :lol:

Я уже упоминал, что этот архивчик может компилироваться GCC (по умолчанию), а может и Clang:

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ make
gcc -Wall 1.c -o type

olej@notebook:~/2013_WORK/AntonG/examples$ make clang
clang -xc -Wall 1.c -o typel
А теперь смотрим сюда ... А-л-е-е-е-е-е-е-е-е .. Оп! ;-) :

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ ./type 2
02 ---------------------------------------
максимальная точность итерационных вычислений:
для float: 1.000000e-20 (число итераций 20)
для double: 1.000000e-20 (число итераций 20)
------------------------------------------

olej@notebook:~/2013_WORK/AntonG/examples$ ./typel 2
02 ---------------------------------------
максимальная точность итерационных вычислений:
для float: 9.999999e-09 (число итераций 8)
для double: 9.999999e-09 (число итераций 16)
------------------------------------------
Точность итерационных вычислений изменилась ... на 10 порядков! :-o
P.S. Но с этим нужно ещё очень сильно разбираться!!!

Ну как? Вам ещё не кажутся такие тесты весёлыми? ;-)
Мне - кажутся.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 15:43

Olej писал(а):Ну как? Вам ещё не кажутся такие тесты весёлыми? ;-)
Мне - кажутся.
Но и это ещё не всё!
Результаты вещественных вычислений близко к границе потери точности могут отличаться в зависимости от порядка записи вычислений в выражении... Отличаться так ... на 1-2 порядка :lol: .

Сделал ещё тест как вариант предыдущего (он есть в файле 1.с):

Код: Выделить всё

void test022( void ) {    // зависимость от порядка записи вычислений
   printf( "результат зависит от порядка записи вычислений:\n" );
   int i;
   double x;
   for( i = 0, x = 1.; ; i++ ) {
      if( (float)1. + x == (float)1. + x / (float)DIV ) break;
      x /= (float)DIV;
   }
   printf( "вариант #1\t: %e (число итераций %d)\n", x, i );
   for( i = 0, x = 1.; ; i++ ) {
      double y1 = x + 1., y2 = ( x /= (double)DIV ) + 1.;
      if( y1 == y2 ) break;
   }
   printf( "вариант #2\t: %e (число итераций %d)\n", x, i );
}
Здесь в 2-х случаях тип epsilon (он здесь x ;-) ) один и тот же, double, но вот порядок записи вычислений 1+ epsilon и 1 + epsilon / 10 - разный.
И вот итог:

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ ./type 3
03 ---------------------------------------
результат зависит от порядка записи вычислений:
вариант #1: 1.000000e-20 (число итераций 20)
вариант #2: 1.000000e-17 (число итераций 16)
------------------------------------------

olej@notebook:~/2013_WORK/AntonG/examples$ ./typel 3
03 ---------------------------------------
результат зависит от порядка записи вычислений:
вариант #1: 1.000000e-16 (число итераций 16)
вариант #2: 1.000000e-17 (число итераций 16)
------------------------------------------
Ну, то что с Clang происходит - это выше человеческого понимания ;-) .
Но GCC то, GCC ... Во даёт! :lol:

А поменяйте опцию оптимизации (-O, -O1, -O2, -O3, ...) компилятора! - что там почнётся!!!? :-o

А проделайте такое под MinGW или Cygwin ... :lol:

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 15:54

Olej писал(а):Ну как? Вам ещё не кажутся такие тесты весёлыми? ;-)
Мне - кажутся.
Я не утверждаю, что показанные результаты достоверные.
Там могут быть в коде тонкие ошибки, которые нужно поправить - это только самые черновые наброски кода ... "из печки".

Но такие тесты задают много вопросов по семантике языка C.
Но это тесты, конечно, не учащегося, а тесты семантики языка С и компилятора (реализации).

Аватара пользователя
Виктория
Писатель
Сообщения: 113
Зарегистрирован: 28 дек 2012, 14:05
Откуда: Самара
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Виктория » 10 ноя 2013, 16:07

Ох, боюсь, что моим студентам объяснение этого математического парадокса будет не по зубам. :-o
Когда результат зависит от порядка записи вычислений и мы не уверены в распределении приоритетов используемым компилятором, точно не стоит жалеть скобок. Я бы ещё и в промежуточный листинг ассемблера заглянула, чтобы перепроверить как отрабатывает компилятор.

Аватара пользователя
Виктория
Писатель
Сообщения: 113
Зарегистрирован: 28 дек 2012, 14:05
Откуда: Самара
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Виктория » 10 ноя 2013, 16:31

Olej писал(а):Кстати, с какими компиляторами с языка C работают и с какими знакомятся студенты на 4 курсе, изучающие ПО микропроцессорных систем? ;-)
Надо бы с gcc и IAR.

Аватара пользователя
Виктория
Писатель
Сообщения: 113
Зарегистрирован: 28 дек 2012, 14:05
Откуда: Самара
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Виктория » 10 ноя 2013, 18:05

Olej писал(а):
Olej писал(а):Ну как? Вам ещё не кажутся такие тесты весёлыми? ;-)
Мне - кажутся.
Но и это ещё не всё!
Результаты вещественных вычислений близко к границе потери точности могут отличаться в зависимости от порядка записи вычислений в выражении... Отличаться так ... на 1-2 порядка :lol: .

Сделал ещё тест как вариант предыдущего (он есть в файле 1.с):

Код: Выделить всё

void test022( void ) {    // зависимость от порядка записи вычислений
   printf( "результат зависит от порядка записи вычислений:\n" );
   int i;
   double x;
   for( i = 0, x = 1.; ; i++ ) {
      if( (float)1. + x == (float)1. + x / (float)DIV ) break;
      x /= (float)DIV;
   }
   printf( "вариант #1\t: %e (число итераций %d)\n", x, i );
   for( i = 0, x = 1.; ; i++ ) {
      double y1 = x + 1., y2 = ( x /= (double)DIV ) + 1.;
      if( y1 == y2 ) break;
   }
   printf( "вариант #2\t: %e (число итераций %d)\n", x, i );
}
Здесь в 2-х случаях тип epsilon (он здесь x ;-) ) один и тот же, double, но вот порядок записи вычислений 1+ epsilon и 1 + epsilon / 10 - разный.
И вот итог:

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ ./type 3
03 ---------------------------------------
результат зависит от порядка записи вычислений:
вариант #1: 1.000000e-20 (число итераций 20)
вариант #2: 1.000000e-17 (число итераций 16)
------------------------------------------

olej@notebook:~/2013_WORK/AntonG/examples$ ./typel 3
03 ---------------------------------------
результат зависит от порядка записи вычислений:
вариант #1: 1.000000e-16 (число итераций 16)
вариант #2: 1.000000e-17 (число итераций 16)
------------------------------------------
Ну, то что с Clang происходит - это выше человеческого понимания ;-) .
Но GCC то, GCC ... Во даёт! :lol:

А поменяйте опцию оптимизации (-O, -O1, -O2, -O3, ...) компилятора! - что там почнётся!!!? :-o

А проделайте такое под MinGW или Cygwin ... :lol:
Может ещё стоит попробовать разбить исходный тест на два теста? Один с float, а другой с double

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 22:04

Виктория писал(а):Ох, боюсь, что моим студентам объяснение этого математического парадокса будет не по зубам. :-o
А им, может быть, и не нужно объяснение во всех деталях (т.е. кому-то нужно, кому интересно, а кому-то и не нужно), но нужно бы показать такие фрагменты кода, ознакомить...

Но показать такие фрагменты-тексты - очень полезно: останется на уровне рефлексов, даже забыв почему это так: "никогда не сравнивать в if вещественные значения на равенство..." и др. т.д. и т.п.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 10 ноя 2013, 22:18

Olej писал(а):Но показать такие фрагменты-тексты - очень полезно:
Кроме того, есть ещё одна ... сторона:
- язык C изучают (и преподают) в большинстве по книге K&R в каком-нибудь #-надцатом издании ...
- и оно того стоит!

Но после K&R (образца 1970г.) и даже после других более поздних книг в C происходило достаточно много изменений-дополнений - C99:
После процесса стандартизации ANSI спецификация языка Си некоторое время оставалась относительно неизменной, тогда как C++ продолжал развиваться, особенно во время его стандартизации. Нормативная поправка 1 создала новый стандарт языка Си в 1995 году, но только с точки зрения исправления некоторых деталей стандарта C89 и добавления расширенной поддержки интернациональных наборов символов. Тем не менее, стандарт подвергся дальнейшей ревизии в конце 1990-х, что привело к публикации стандарта ISO/IEC 9899:1999 в 1999 году. Этот стандарт часто упоминается как «C99». Он был принят в качестве стандарта ANSI в мае 2000 года.
...
C99 — современный стандарт языка программирования Си. Определен в ISO/IEC 9899:1999, современная версия - ISO/IEC 9899:1999/Cor 3:2007 от 2007-11-15. Является развитием стандарта C90.


Так вот после обстоятельного изучения C K&R хорошо бы всякому ещё разобраться с основными дополнениями поздними ... но никто стандарты, естественно, читать не станет.

Вот для этого тоже хорошо бы просто показать вот такие мини-примеры, в которых используются эти новые возможности, чтобы "зацепиться" за их написание (например, тех же комплексных чисел) ... а дальше оно уже "легче пойдёт" ;-) , тем же поиском в интернете ... когда понятно уже что искать...
Вот такого уже достаточно, чтобы подтолкнуть любопытство (хотя бы на инициализацию посмотреть):

Код: Выделить всё

void test042( void ) {    // комплексные C99
   double complex zc = 1. + 1. * I;
   printf( "типы данных ISO C99:\n" );
   printf( "комплексное число: %f %c %fi\n",
           creal( zc ), cimag( zc ) >= 0 ? '+' : '-', cimag( zc ) );
   long double ab = cabsl( zc );
   printf( "модуль этого числа: %Lf\n", ab );
}

Код: Выделить всё

olej@notebook:~/2013_WORK/AntonG/examples$ ./typel 5 6
05 ---------------------------------------
типы данных ISO C99:
- long double,  размер 12       : 3.141592653590e+00 - 3.141592653590e+00 -> 0.000000000000e+00
- double     ,  размер 12       : 3.141592653590e+00 - 3.141592653590e+00 -> 0.000000000000e+00
- float      ,  размер 4        : 3.141592741013e+00 - 3.141592653590e+00 -> 8.742277657348e-08
06 ---------------------------------------
типы данных ISO C99:
комплексное число: 1.000000 + 1.000000i
модуль этого числа: 1.414214
------------------------------------------
Вложения
1.c
(4.86 КБ) 263 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: язык C в Linux: вопросы начального уровня

Непрочитанное сообщение Olej » 11 ноя 2013, 12:42

Olej писал(а): Вот для этого тоже хорошо бы просто показать вот такие мини-примеры, в которых используются эти новые возможности, чтобы "зацепиться" за их написание (например, тех же комплексных чисел) ... а дальше оно уже "легче пойдёт" ;-) , тем же поиском в интернете ... когда понятно уже что искать...
Вот такого уже достаточно, чтобы подтолкнуть любопытство (хотя бы на инициализацию посмотреть):
"любопытство" я здесь употребил в том смысле, что в большинстве учебников-руководст по языку C я вообще не вспомню где бы упоминали о наличии даже такого целого раздела, как комплексная математика - complex.h.
Именно не в C++, это общеизвестно, а в классическом C.
(Просто по той причине, что целый этот раздел появился гораздо позже "остального" C ... и даже позже написания учебников. ;-) )

А комплексные вычисления - это огромная область всех электротехнических и радиотехнических расчётов (кто пересекался)...
Но не только.
В виде complex можно с успехом представлять 2-х мерные геометрические координаты (x,y) в картографии ... и любой планиметрии, в разрабатываемых компьютерных игрушках - потому что complex это и есть представление на комплексной плоскости, и комплексные операции (такие как +, -, abs(), ...) соответствуют естественному определению метрики в планиметрии.

Но и это не всё... Можно искусственно отображать в complex любые сущности, которые представляются 2-мя численными компонентами. Вот недавно мне нужно было по-быстренькому написать иллюстрацию функции решений квадратных уравнений. И вот почему бы её не представить таким прототипом:

Код: Выделить всё

complex double sqrt_roots( double A, double B, double C ) {
...
}


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

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 7 гостей