Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > PHP: Общие вопросы > Проблема с переводом из double в integer


Автор: Icaros 7.1.2007, 21:43
Добрый день, уважаемые. 
Столкнулся с серьезнейшей проблемой. Уже приличное время назад столкнулся с такой штукой: есть массив с данными, состоящий из 100 элементов, в цикле обращаюсь к элементам для их вывода. Вот код:
Код

// $answ = array(0, 1, 2, 3, 4, ...)
$i = 0;
$dx = 0.01;
$L = 1;
for ($i=0; $i<L; $i+=$dx)
{
   echo $answ[$i*100];
}

И вот это долгое время у меня имеется странный эффект:
Код

Вывод программы:
0, 1, 2, ..., 9, 10, 10, 11, 12, 13, 15, 16...
 
Поначалу был, мягко скажем, в шоке...
После экспериментов с пошаговым проходом по коду (спасибо Zend`у) обнаружил интересную особенность. При переводе числа из double в integer ПХП исходит из своих соображений, не совпадающих с моими. Т.е. число 0.11*100, переведенное в integer, станет 10. Вы спросите: а зачем так странно обращаться к массиву (т.е. 0,0х * 100)? Может и странно, но я был уверен, что нет никакой разницы и так было удобнее. Вот эта особенность морочила мне голову такое время...  smile
В одном из журналов  PHPinside я читал (вот сейчас вдруг вспомнил, а тогда не придал значения), что в ПХП особый способ представления double (или integer) чисел. Там было сказано, что выражение 0 == 0.0 выдаст false (или как-то так) и надо пользоваться неким math (может ошибаюсь, но я не в курсе что это).
Если быть короче, то как корректно перевести из double в integer ? Или, если это все ахинея, скажите, чтобы я учил учебник ПХП за первый класс церковно-приходской школы. 

Автор: vasac 7.1.2007, 21:53
Лично я не могу сказать с полным на то основанием, что это ахинея, по той причине, что слабо понял о чем здесь написано.
Опишите, пожалуйста, саму суть проблемы без кода этого ужасного цикла.

Цитата(Icaros @  7.1.2007,  21:43 Найти цитируемый пост)
в ПХП особый способ представления double (или integer) чисел

Возможно Вы имеете ввиду особенности представления чисел с плавающей запятой, то да, double вообще сравнивать по большому счету нельзя, даже с double. Но это проблема (проблема ли) отнюдь не только php.

Автор: murod 7.1.2007, 22:11
да есть такой глюк,
тебе надо так:
Код

echo $answ["".$i*100];

может и не глюк, но у меня нет обяснений.  smile  

Автор: Icaros 7.1.2007, 22:40
Вот что получалось у меня:

Код:
Код

$c = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
$txt = "";
$j = 0;

for($i=0; $i<0.17; $i+=0.01)
{
     $txt .= (string)($j++)." : "; 
     $txt .= (string)$c[$i*100];
     $txt .= "<br />";
}

echo $txt;

Результат:
Код

0 : 0
1 : 1
2 : 2
3 : 3
4 : 4
5 : 5
6 : 6
7 : 7
8 : 8
9 : 9
10 : 10
11 : 10
12 : 11
13 : 12
14 : 13
15 : 15
16 : 16


Уважаемый murod, Вы совершенно правы. Спасибо. Чудеса. С Вашим хаком все встало на круги своя. Что же это за "особенность", попортившая мне нервы ?..

Автор: Mal Hack 7.1.2007, 22:45
Icaros, что за бред вы тут несете?
Я не понимаю, что религия не позволяет писать по человечески, как большинство нормальных людей?
Код
// $answ = array(0, 1, 2, 3, 4, ...)
$i = 0;
$L = 100;
for ($i=0; $i<L; $i++)
{
   echo $answ[$i];
}


Автор: murod 7.1.2007, 23:12
Mal Hack,  мне кажется Icaros просто забыл поставить знак $. ничего страшного. ты лучше скажи свое мнение что это такое? глюк или это мы- не из "большинства нормальных людей"? smile

Добавлено @ 23:15 
с типом String все работает правильно, а с Integer глюки

Автор: Mal Hack 7.1.2007, 23:21
murod, я уже привел свою точку зрения касательно кода в первом посте.

Автор: murod 7.1.2007, 23:41
Mal Hack, кажется ты немножка не понял проблему 
Icaros'а 
Код

<?php
$i = 0;
$dx = 0.01;
$L = 0.12;
$arr=array(0,1,2,3,4,5,6,7,8,9,10,11,12);
for ($i=0; $i<$L; $i+=$dx)
{
 $g=(100*$i);
 echo($arr[$g]."<br>");
}
?>

выводит:
0
1
2
3
4
5
6
7
8
9
10
10
11

а этот код :
Код

<?php
$i = 0;
$dx = 0.01;
$L = 0.12;
$arr=array(0,1,2,3,4,5,6,7,8,9,10,11,12);
for ($i=0; $i<$L; $i+=$dx)
{
 $g=(String)(100*$i);
 echo($arr[$g]."<br>");
}
?>

выводит:
0
1
2
3
4
5
6
7
8
9
10
11
12
именно с массивами работает не правильно!!
вот без массива:
Код

<?php
$i = 0;
$dx = 0.01;
$L = 0.12;
for ($i=0; $i<$L; $i+=$dx)
{
 $g=(100*$i);
 echo($g."<br>");
}
?>

выводит правильно:
0
1
2
3
4
5
6
7
8
9
10
11
12

Добавлено @ 23:42 
Народ как это понять?

Автор: vasac 7.1.2007, 23:50
murod
техническая проблема, как я уже указал, в представлении чисел с плавающей запятой. Насчет ее понимания — в поиск.
глобальная же проблема в том, что вы творите здесь какую-то фигню и пытаетесь понять, почему же в итоге фигня и получается.

Автор: Mal Hack 7.1.2007, 23:58
murod, я прекрасно понял проблему, жаль модераторский статус е позволяет сказать где у автора ошибка...

Цитата(murod @  7.1.2007,  23:41 Найти цитируемый пост)
а этот код :

ИДИОТИЗМ !!!

Автор: skyboy 8.1.2007, 00:27
Mal Hack, но ведь такая проблема(а она, по-видимому, в наличии) вполне может выплыть в другом, внешне "вполне приличном" коде. Не в курсе, от чего такая ерунда при работе с хэшами?

Автор: Mal Hack 8.1.2007, 01:03
Какие хэши?
Код

<?php

$c = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
$txt = "";
$j = 0;
for($i=0.0; $i<0.17; $i+=0.01)
{
    print $g = ((float)100.0*(float)$i);  
    $index = number_format($g,2, ',', ' ');
    
    print "=======".$c{(int)$g}."  ======== " .$c{(int)$index}."   ====  ".($i*100)." ==== ".$i." <br/>";
}
?>


Судя по всему это баг в двоичном представлении числа...
К сожалению, как хранятся числа во внутреннем представлении - вопрос, ответ на которой знают очень мало людей.

Автор: ewolf 8.1.2007, 01:19
Причина такой ошибки, как мне кажется, состоит в следующем:

Из-за того, что числа в формате IEEE представляются в двоичной форме в формате [экспонента][мантисса]. В результате, некоторые десятичные дроби не могут быть точно представлены в этой записи. Об этом сказано тут например http://www.php.net/manual/ru/language.types.float.php 
В результате при постепенном добавлении к переменной значения 0.01 к 11 шагу накапливается ошибка, и вместо числа 11 мы храним число 10.9999999999999999. 

Ключ может быть только либо строкой, либо целым числом, поэтому у числа 10.(9) отбрасывается дробная часть. И мы получаем значение 10.

При преобразовании в строку алгоритм окруления другой и преобразует дробное число в целое, основываясь на количестве разрядов после запятой. 

Сравните, например

Код

echo 10.9999999999; // = 10.9999999999
echo 10.99999999999; // = 11


В этом и причина "ошибки"

Автор: Mal Hack 8.1.2007, 01:27
Хм... Знал я конечно, о такой фишке, но не думал, что в PHP на столько это криво реализовано..
Код

<?php

$c = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
$txt = "";
$j = 0;
for($i=0.0; $i<0.17; $i+=0.01)
{
    print $g = ((float)100.0*(float)$i);  
    $index = number_format($g,2, ',', ' ');
    
    print "=======".sprintf("%10.250e",$g)."=======".$c{(int)$g}."  ======== " .$c{(int)$index}."   ====  ".($i*100)." ==== ".$i." <br/>";
}
?>

на выходе получаем:
Цитата

0    0.000000000000000000000000000000000000000e-1    0    0    0    0 
1    1.000000000000000000000000000000000000000e+0    1    1    1    0.01 
2    2.000000000000000000000000000000000000000e+0    2    2    2    0.02 
3    3.000000000000000000000000000000000000000e+0    3    3    3    0.03 
4    4.000000000000000000000000000000000000000e+0    4    4    4    0.04 
5    5.000000000000000000000000000000000000000e+0    5    5    5    0.05 
6    6.000000000000000888178419700125232338905e+0    6    6    6    0.06 
7    7.000000000000000888178419700125232338905e+0    7    7    7    0.07 
8    8.000000000000000000000000000000000000000e+0    8    8    8    0.08 
9    9.000000000000000000000000000000000000000e+0    9    9    9    0.09 
10    1.000000000000000000000000000000000000000e+1    10    10    10    0.1 
11    1.099999999999999822364316059974953532219e+1    10    11    11    0.11 
12    1.199999999999999822364316059974953532219e+1    11    12    12    0.12 
13    1.299999999999999822364316059974953532219e+1    12    13    13    0.13 
14    1.399999999999999822364316059974953532219e+1    13    14    14    0.14 
15    1.500000000000000000000000000000000000000e+1    15    15    15    0.15 
16    1.600000000000000000000000000000000000000e+1    16    16    16    0.16 

Хм....
Вот написал тестик на С++...
Код
int _tmain(int argc, _TCHAR* argv[])
{
    int c[17] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    double i,g;
    int index;

    for(i=0.0; i<0.17; i+=0.01)
    {
        g = ((float)100.0*(float)i);
        printf( "%2.2f\t", g);  

        index = (int)(g);

        printf( "%2.20f\t\t%d\t%d\t%2.2f\t%2.2f\n",g,c[(int)g],c[(int)index],(i*100),i);
    }

    return 0;
}


Результаты:
Цитата

0.00    0.00000000000000000000          0       0       0.00    0.00
1.00    0.99999997764825821000          0       0       1.00    0.01
2.00    1.99999995529651640000          1       1       2.00    0.02
3.00    2.99999993294477460000          2       2       3.00    0.03
4.00    3.99999991059303280000          3       3       4.00    0.04
5.00    5.00000007450580600000          5       5       5.00    0.05
6.00    5.99999986588954930000          5       5       6.00    0.06
7.00    7.00000002980232240000          7       7       7.00    0.07
8.00    7.99999982118606570000          7       7       8.00    0.08
9.00    9.00000035762786870000          9       9       9.00    0.09
10.00   10.00000014901161200000         10      10      10.00   0.10
11.00   10.99999994039535500000         10      10      11.00   0.11
12.00   11.99999973177909900000         11      11      12.00   0.12
13.00   12.99999952316284200000         12      12      13.00   0.13
14.00   14.00000005960464500000         14      14      14.00   0.14
15.00   15.00000059604644800000         15      15      15.00   0.15
16.00   15.99999964237213100000         15      15      16.00   0.16


Автор: skyboy 8.1.2007, 02:37
да уж. почему бы не округлять-то? спасибо за приведенные тесты, Mal Hackewolf.
Кстати, Mal Hack, всё же не только в кривости кода дело, верно?  smile 

Автор: Mal Hack 8.1.2007, 02:52
skyboy, я изначально смотрю на задачу, и вижу, что работа с массивом ведется через одно место. Соответственно и реакция такая, на код, который реально на практике не применяется, т.к. бредовый. 
Значит автор так хорошо делает акцент при составлении поста...

Цитата(skyboy @  8.1.2007,  02:37 Найти цитируемый пост)
Кстати, Mal Hack, всё же не только в кривости кода дело, верно?  smile  

Да, конечно. Только не в "кривости" кода, а в "глупости" кода...
Это все равно что 
Код
$b = 5;
$a = (int)(double)(float)$b;
.

Цитата(skyboy @  8.1.2007,  02:37 Найти цитируемый пост)
спасибо за приведенные тесты, Mal Hack, ewolf.

Всегда пожалуйста.

Автор: murod 8.1.2007, 09:47
я провел вот такой эксперимент,
Код

<?php
$i = 0;
$dx = 0.01;
$L = 0.16;
for ($i=0; $i<$L; $i+=$dx)
{
 $float_number=(100*$i);
 $int_number=(int)$float_number;
 $string_number=(string)$float_number;
 printf("%01.19f -> %01.1f -> %s <br>", $float_number,$int_number,$string_number);
}
?>

этот код выводит:
0.0000000000000000000 -> 0.0 -> 0
1.0000000000000000000 -> 1.0 -> 1
2.0000000000000000000 -> 2.0 -> 2
3.0000000000000000000 -> 3.0 -> 3
4.0000000000000000000 -> 4.0 -> 4
5.0000000000000000000 -> 5.0 -> 5
6.0000000000000008882 -> 6.0 -> 6
7.0000000000000008882 -> 7.0 -> 7
8.0000000000000000000 -> 8.0 -> 8
9.0000000000000000000 -> 9.0 -> 9
10.0000000000000000000 -> 10.0 -> 10
10.9999999999999982236 -> 10.0 -> 11
11.9999999999999982236 -> 11.0 -> 12
12.9999999999999982236 -> 12.0 -> 13
13.9999999999999982236 -> 13.0 -> 14
15.0000000000000000000 -> 15.0 -> 15 
отсюда несложно уловить, при преобразовании типoв с плаваюшей запятой на целые числа
просто отбрасывается дробняя часть(числа после запятой).
вот в этом было и фокус!

Автор: Icaros 8.1.2007, 20:47
Добрый день, уважаемые пользователи. И отдельное приветствие уважаемому  Mal Hack`у.
Спасибо вам всем за помощь.
 
И отдельно г-ну Mal Hack`у.
Трудно поспорить с Вашим мастерством программирования, ровно как и с тем, что я не профи. По поводу первого поста - Вы скорее всего правы, уважаемый Mal Hack. С сочинительством у меня беда, да и не программист по образованию... НО, насколько бы "бредовым" не была приведенная программа (кстати, более корректна вторая, приведенная мной), проблема морочила мне голову очень долгое время. На момент создания программы для меня была удобна именно такая форма обращения к массиву. Да и спрашивал я не про уровень убогости, а про странный эффект. Мне и в голову не приходило, что такое может быть. 
Делаю программу в учебных (просьба не ржать) целях. К сожалению, кроме вашего форума спросить не у кого. Да к тому же я не показывал вам всей программы, и приведенный мною код является упрощенной версией, предназначенной только для демонстрации эффекта. 

P.S. Еще раз спасибо за помощь.

Автор: Mal Hack 8.1.2007, 21:42
Цитата(Icaros @  8.1.2007,  20:47 Найти цитируемый пост)
На момент создания программы для меня была удобна именно такая форма обращения к массиву.

А чем не удобная форма, когда итерационный цикл идет по целым значениям? Я вот этого понять не могу?
Я могу понять, когда такое надо применить, к примеру, для мат расчетов (интеграл посчитать), но тут - извольте...

Автор: smartov 16.1.2007, 00:32
Тема жжот. Не знал что такая уловка есть  smile  Ужас.

Автор: DENNN 16.1.2007, 00:52
ИМХО, конечно и сейчас меня закидают помидорами, но тема наглядно показывает одну очень интересную истину: "чистые" PHP-программисты зачастую совершенно не понимают ни как работает компьютер, ни что же реально происходит с их данными. По чистыми  я понимаю людей, кто сразу начал с PHP, не занимаясь более "тяжелыми" языками. Фактически ответ был указан в одном из первых ответов. Но большая часть спорящих совершенно не обратила на него внимания. Любой же сишник, дельфист, ассемблерщик (и прочие языки, где необходимо заглядывать в способы хоранения данных, утсройство процуедур и т.д. и т.п.)  догался бы сразу без подсказок. Я уж не говорю о таких граблях как прописная истина "вещественные числа нельзя сравнивать операциями точного равенства, только после округления с заданной потерей точности".

Обратите внимание при случае, как неимоверно тяжело дается "читстому PHP-исту" осваивание азов C++ и насколько легко и даже с некоторым недоумением к простоте, граничащей с небрежностью, получается у професионального C++ика начинать работать с PHP. Самое примечательное, что синтаксис PHP чуть ли не скопирован с C.

Кидайте помидоры, я готов smile

Добавлено @ 00:54 
P.S. для интертесующихся - причину неверной работы кода понял практически недочитав до конца первый абзац, но так интересно было следить за дискуссией! 

Автор: Mal Hack 16.1.2007, 01:13
DENNN, согласен с тобой во всем, кроме:

Цитата(DENNN @  16.1.2007,  00:52 Найти цитируемый пост)
как неимоверно тяжело дается "читстому PHP-исту" осваивание азов C++

Я начал PHP и паскаль практически одновременно, Си потом освоил (азы, конечно), довольно-таки легко, столкнувшись с этими проблемами на Delphi...

Еще играет роль опыт... Если все время "клепать" приложения на vcl, на Delphi, к примеру, то таких вещей ты как не знаешь так знать и не будешь.

Автор: smartov 16.1.2007, 10:26
DENNN
Цитата(DENNN @  15.1.2007,  23:52 Найти цитируемый пост)
Кидайте помидоры

Чмяк-чмяк... чмяк.... smile

Как говорил Горбачёв "Боря, ты не прав".
Видел я PHP код, который пишут наСильники, никогда на php не писавшие smile То еще зрелище. Все пытаются сделать руками и через ж (как в С привыкли). Все пытаются память освобождать итд итп. 
Так что у каждого языка своё. У PHP своя задача и с задачей C она не пересекается, и устраивать холивар по этому поводу смысла не вижу. 

Так что ящик помидорав тебе в подарок smile

Автор: DENNN 16.1.2007, 12:21
Цитата(Mal Hack @  16.1.2007,  01:13 Найти цитируемый пост)
начал PHP и паскаль практически одновременно

ЧТо уже выбивает из общей тенденции. smile

smartov, спасибо за помидоры. 

Автор: Mal Hack 16.1.2007, 13:16
Цитата(DENNN @  16.1.2007,  12:21 Найти цитируемый пост)
ЧТо уже выбивает из общей тенденции. smile

Знания PHP помогали осваивать Pascal... Я сначала на PHP многое делал, уже потом на Pascal'e.

Автор: szz 19.1.2007, 20:42
Позвольте немного приложиться.
  Я как раз из тех, кто на PHP и на Perle писал все ручками, переходя с Си smile например так:
Код

// надо скопировать строку а в b
$a = "the string content";
$b = "";
for($i=0;$i<strlen($a);++$i)
   $b[$i] = $a[$i];

 smile Утрирую конечно, но все же smile

но все же можно вполне писать на Си, понятия не имея о внутреннем устройстве doudle и float.
Чего не скажешь, например, о бестиповых языках типа Би (непосредственного предшественника Си). Там есть только один тип - слово - то есть два байта. 
И если вам нужно дробное, то самому приходилось реализовывать, хочешь - плавающей, хочешь - фиксированной точкой. Нужны символы, сроки, указатели - сам думац как реализовать. Как сам с собой договоришься.  smile 
 И Бишники (если такие ее существуют) над от души бы над этой темой посмеялись smile

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