От: проблемы программы в цикле for с десятичной дробью

Автор kol1978, 09 января 2023, 23:12:22

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

kol1978

Обнаружил :o , что проблемы программы в цикле for >:( , стал смотреть и выделил код в другой файл, запустил... получил «странные» результаты. :-X 
Может ктонибуть пояснить полечаемые результаты?

 В цикле применил число с плавающей точкой (смотрел книжку* это не возбраняется вроде; но не декремент или инкремент, а увеличение или уменьшение на десятичную дробь – 0.01), но перебор идет какими то странными рывками: -262143.859375
-262143.875000
-262143.890625
-262143.906250
-262143.921875
-262143.937500
-262143.953125
-262143.968750
-262143.984375
-262144.000000
min float : -262144.000000
Может кто пояснить данный эффект или (и) показать где об этом почитать? Главное как обходить такие вещи? Из прилагаемого примера:
"min float 0.1: -167771.968750
min float 0.01: -262144.000000" это типа накопленная погрешность что ли??? числа в два раза отличаются...
register float dfF;
register float dfA = 1;
register float dfB = 1;
   for (dfF = -1; dfF > -167772.000000; dfF -= 0.1){dfA = dfF;
                                   // dfF = dfF - 1;
    if (dfA == dfB) break;// dfF -=0.01 
//1            printf("%f\n", dfF);
        dfB = dfF;}
           printf("\n min  float 0.1: %f\n", dfB); 
     for (dfF = -1; dfF > -1677720.000000; dfF -= 0.01){ dfA = dfF;
if (dfA == dfB) break;// dfF -=0.01 
//2    printf("%f\n", dfF);
[code]



Далее сделал цикл на основе декремента целого числа и в теле цикла стал уменьшать десятичную дробь на 0,01 (0,1):
!!! float -0.1: 70.000
!!! float -0.01: 69.997   - FLT_DIG  float : 6 это значит шесть значащих цифр, а тут их всего пять - куда делись три тысячных 0,003? .. разница в цифрах тоже весьма существенная хотя само число не большое.
" FLT_MIN  float : 0.00000000000000000000
FLT_DIG  float : 6
DBL_DIG  float : 15
LDBL_DIG float : 18
FLT_EPSILON  float : 0.000"   
[code]register long dfF;
register float dfA = 0;
register float dfB = 0;
   
   for (dfF = 0; dfF < 700; dfF++){       // 2147483647 максимальное число в цикле
dfA += 0.1;

       }
           printf("\n!!! float -0.1: %.3f\n", dfA);
 for (dfF = 0; dfF < 7000; dfF++){
dfB += 0.01;

        }
           printf("\n!!! float -0.01: %.3f\n", dfB);
[code]


.. разница в цифрах тоже весьма существенная, хотя само число не большое для типа float  - почему так и где об этом можно подробно узнать? PS  Использовать в контроле цикла десятичную дробь – это «самоубийство» и (или) как минимум программа аппаратно зависима!

Еще одно PS:
Если применить тип double то «все хорошо»  - понятно, что здесь дело в размерах используемого двоичного представления числа! НО! Это как то должно быть «очевидно» для человека пишущего на си под конкретную платформу – и где узреть эту очевидность!?
[code]register long dfF;
register double dfA = 0;
register double dfB = 0;
 
   
   for (dfF = 0; dfF < 700; dfF++){       // 2147483647
dfA -= 0.1;

       }
           printf("\n!!! float -0.1: %.6f\n", dfA);
 for (dfF = 0; dfF < 7000; dfF++){
dfB -= 0.01;

        }[code]

 
Книга*: https://cpp.com.ru/kr_cbook/ch1kr.html#p13
*" Brian W. Kernighan, Dennis M. Ritchie The C programming Language Second Edition AT&T Bell Laboratories Murray Hill, New Jersey Prentice Hall PTR, Englewood Cliffs, New Jersey 07632":
"
1.5.2 Подсчет символов
...
Программа подсчета символов накапливает сумму в переменной типа long. Целые типа long имеют не менее 32 битов. Хотя на некоторых машинах типы int и long имеют одинаковый размер, существуют, однако, машины, в которых int занимает 16 бит с максимально возможным значением 32767, а это - сравнительно маленькое число, и счетчик типа int может переполниться. Спецификация %ld в printf указывает, что соответствующий аргумент имеет тип long.
Возможно охватить еще больший диапазон значений, если использовать тип double (т. е. float с двойной точностью). Применим также инструкцию for вместо while, чтобы продемонстрировать другой способ написания цикла.
#include <stdio.h>
[code]/* подсчет вводимых символов; 2-й версия */
main()
{
double nc;
for (nc = 0; getchar() != EOF; ++nc)
;
printf("%.0f\n", nc);
}[code]

В printf спецификатор %f применяется как для float, так и для double; спецификатор %.0f означает печать без десятичной точки и дробной части (последняя в нашем случае отсутствует..."


[size=1][i][time]10 января 2023, 00:16:42[/time][/i][/size]
"Однажды у меня был фрагмент кода, который пытался вычислить количество байтов в буфере с помощью выражения (k * 1024), где k было значением int, представляющим количество килобайт. К сожалению, это было на машине, где int оказался 16-битным. С тех пор как k и 1024 оба были int, продвижения по службе не было. При значениях k >= 32 произведение было слишком большим, чтобы поместиться в 16-битном int, что приводило к переполнению. Компилятор может делать все, что захочет, в ситуациях переполнения - обычно биты старшего порядка просто исчезают." - что то в этом есть... :P   - http://cslibrary.stanford.edu/101/EssentialC.pdf

[size=1][i][time]10 января 2023, 02:09:17[/time][/i][/size]
Все что пока удалось найти: "Одинарная точность равна примерно 6 цифрам точности, а двойная - примерно 15 цифрам точности. Большинство программ на языке Си используют double для их вычисления. Основная причина использования float заключается в экономии памяти, если необходимо сохранить много чисел. Главное, что нужно помнить о числах с плавающей запятой, - это то, что они неточны. Например, каково значение следующего двойного выражения:
(1.0/3.0 + 1.0/3.0 + 1.0/3.0) // точно ли это равно 1.0?
Сумма может быть или не быть точно равна 1,0, и она может варьироваться от одного типа машины к другому. По этой причине вы никогда не должны сравнивать числа с плавающей запятой друг с другом для равенства (==) - вместо этого используйте сравнения с неравенством (<). Поймите, что правильная программа на языке Си , выполняемая на разных компьютерах, может выдавать несколько разные выходные данные в крайних правых цифрах своих вычислений с плавающей запятой. Присвоение типа с плавающей запятой целочисленному типу приведет к удалению дробной части числа. "