Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Алгоритмы > Странности при подсчете чисел.....


Автор: Wowa 11.2.2004, 02:07
Создал я значит цикл:

for ($i = 7.12; $i <= 10; $i=$i+0.01) {
...
}

При его выполнении выясняется, что компьютер прибавляет к $i 0.01 при каждом проходе цикла, но иногда компьюет прибавляет не 0.01, а меньшее число.
У меня на компьютере комп досчитал до: $i=9.51 и следуюшее значение выдало $i=9.5199999999999
Далее:
9.5299999999999
9.5399999999999
....

С чем это связано, как объясняется и как с этим бороться?

Автор: Wowa 11.2.2004, 02:09
Запусти я этот код на другом компьютере - результат такой же.

Автор: cardinal 11.2.2004, 02:31
Компьютер не ошибается в этом случае, что подтверждает тот факт, что как ты сам сказал второй компер ведет себя точно также.
Цитата
С чем это связано?

С бинарной арифметикой.
Цитата
Как объясняется?

Объясняется тем, что бинарный система счисления не позволяет выражать дроби. Был бы я в Си, я бы сделал листинг на asm и тогда делал бы последующие выводы, т.к. не уверен чем реализована работа с дробными числами - предполагаю, что этим занимается coprocessor. Но все равно вопрос остается, что с твоим кодом делает compiler.
Цитата
Как с этим бороться?

Если это такой одиночный случай в проге, то я бы сделал так:

for (i = 712; i <= 1000; i++) {
...
}

ну и в момент надобности делал бы операцию div 100 и mod 100 получая таким образом целую часть и дробную. А если это не одиночный случай, то я бы сделал тоже самое несколько раз потому, что ничего более полезного в голову не приходит. smile.gif

p.s. Уже даже Admin нарушает правила форума! Аж три вопроса в раз: "с чем связано?", "как объясняется?" и "как с этим бороться?". biggrin.gif

Автор: maxim1000 11.2.2004, 12:27
Цитата
Объясняется тем, что бинарный система счисления не позволяет выражать дроби.

бинарная система кроме основания ничем не отличается от десятичной и позволяет выражать дроби
причина в том, что одни и теже дроби в одной системе могут иметь простой вид, а в другой становиться периодическими
а периодические дроби компьютер усекает (независимо от того, как там реализована арифметика)

Автор: Wowa 12.2.2004, 22:23
Я сделал цикл:
for ($k = 712; $k <= 1000; $k++) {
$i=$k/100;
...
}

Теперь похоже все Ок. Вычисляется все точно.

Автор: val 13.2.2004, 10:39
Да, эта тема обсуждалась в рубрике С/С++, действительно двоичная арифметика имеет такие ньансы при работе с действительными числами:
Цитата

одни и теже дроби в одной системе могут иметь простой вид, а в другой становиться периодическими, а периодические дроби компьютер усекает (независимо от того, как там реализована арифметика)

Автор: OlegsDP 25.2.2004, 14:28
Можно побаловаться с таким myprog.cpp:

Код

#include <stdio.h>
#include <math.h>
void main (int argc, char* argv[])
{
 if (argc != 3) {
   printf ("use: myprog val1 val2");
 } else {
   double a = atof(argv[1]), b=atof(argv[2]);
   printf (((a + b) != a) ? "AGA!" : "OGO!");
 }
}

Автор: Waters 29.2.2004, 18:15
И в чем смысл "баловства"? smile.gif

Автор: OlegsDP 1.3.2004, 17:01
Можно найти такие два числа, которые "не складываются":

myprog 1 0.000000000000001
AGA!

т.е. "сложились"


myprog 1 0.0000000000000001
OGO!

т.е. "не сложились"

Автор: PILOT 6.3.2004, 15:04
Это связано именно с двоичной арифметикой, хотя если быть точнее то с представлением float...
число float в простом случае (я уменьшил число байт для простоты) имеет формат:
SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM
где:
S - это знак
E - Экспонента (-127...+128)
M - 24бита Мантиссы (на самом деле 23)
И число представляется в виде:
±mantissa &#215; 2^exponent

Например число:
-12.5
будет представлено в виде:
0xC1480000.
Как так получилось?
Разбираемся:
формат числа: SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
в двоичном коде: 11000001 01001000 00000000 00000000
шестнадцатиричный код: 00 00 48 C1
Знак = 1 следовательно это отриц. число.
Значение Экспоненты 10000010b или 130d. Вычитаем 127 (это наше смещение нуля) из 130 получаем 3, это и есть Экспонента.
Видно, что Мантисса выражается числом:
10010000000000000000000b
считается, что перед мантиссой всегда есть единица, в этоге получаем следующее число в двоичном представлении:
1.10010000000000000000000b
Теперь учитываем экспоненту, т.к. она равна трем, то точка сдвинется на три разряда вправо:
1100.10000000000000000000b
это число и есть двоичное представление -12.5.
Проверяем (целая часть):
(1 &#215; 2^3) + (1 &#215; 2^2) + (0 &#215; 2^1) + (0 &#215; 2^0)=12 (учитвая знак: -12)
теперь дробная часть:
10000000000000000000b
(1 &#215; 2^-1) + (0 &#215; 2^-2) + (0 &#215; 2^-3) + … = 0.5
прибавляем:
-(12+0.5)=-12.5
теперь не трудно сообразить, что 0.01 представляется сложнее в мантиссе, чем -12.5, потому что 0.01 это не .0625 или .125. А для представления 0.01 придется использовать все биты мантиссы (т.е. точности не хватит), так или иначе набежит один значащий бит в мантиссе, либо в плюс либо в минус.

СУВ.

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