В этой небольшой статейке расскажу о некоторых неприятных ошибках (багофичах) PHP, которых нужно иногда остерегаться. Случайно заметил в PHP такую вещь - он весьма посредственно обходится со знаками чисел, а также путает int32 и int64, неправильно их определяя. Пример: PHP: $a=4294967294; //0b1111111....11110 print $a.'<br>'; //тут мы видим 4294967294 - int32 без знака $a &= pow(2,32)-1; //0b11111111....1111 - казалось бы - ничего не изменилось, число в бинарном виде хранится то же самое, но: print $a; //тут мы видим -2. Почему PHP стал вдруг считать это число как int32 со знаком? Но это еще не самое удивительное. Можно взять такой пример: PHP: $a=4294967298; //это уже 33-х разрядное число. Видимо, PHP выделяет под него int64. Проверим: print $a.'<br>'; //Да, выводится 4294967298. Если бы было отведено только 32 разряда, то вывелось бы 2 (на рисунке показано) /* 1 |00000000000000000000000000000010 ^ |----- 32 разряда ------ ^ 1 доп. разряд, 33й. */ //а теперь сделаем так - используем побитовое И $a &= pow(2,33)-1; //0b11111..111 - 33 единицы. По сути, опять, все разряды сохраняются... Мы сделали вот что: /* 1 |00000000000000000000000000000010 <- $a & &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& <- & 1 |11111111111111111111111111111111 <- 2^32 - 1 = ================================ 1 |00000000000000000000000000000010 - число такое же, как и в изначальном варианте */ print $a; //PHP выводит число 2! Почему? Он внезапно просто отбросил 33й и все последующие разряды и сделал число снова int32, т.е. оставил 10b? Еще несколько примеров: PHP: $a=0; //понятно, 0. $a |= pow(2,32)-1; //32 единицы print $a; //-1. На каком основании PHP посчитал число $a как signed? PHP: $a=18446744073709551610; //это уж точно int64, занимает 64 разряда $b=18446744073709551610; //абсолютно такое же число print $a.'<br>'; //нормально, выводится $a &= $b; //умножаем друг на друга два int64 побитово! print $a; //0?? Что PHP на этот раз сделал с числом? Куда делись вообще все разряды? Должно было получиться такое же число, как $a и $b в результате! PHP: $a=(1<<31); //это 0b10000.....000, или 2147483648 print $a.'<br>'; //а тут оно стало числом со знаком... -2147483648. printf('%u<br>',$a); //забавно, если принудительно сказать о том, что число беззнаковое. $a=(1<<63); //это число должно быть ровно в 2^32 раза больше, чем предыдущее. print $a; //ах ты ж... опять то же самое... В операции << явно не воспринимаются числа больше тридцати двух разрядов, и PHP просто циклически сдвинул число дважды по кругу... Поэкспериментировав, я сделал вывод, что PHP: 1. Способен спутать знак числа при побитовых операциях. 2. В операциях сдвига и побитовых операциях всегда принимаются во внимание только 32 разряда числа, даже если это int64. Остальные разряды просто отбрасываются. К этим операторам относятся все вроде &, |, ^, ~, <<, >>. Вот кстати еще пример: PHP: $a=4294967298; $a=~$a; //побитово отрицаем все разряды числа $a=~$a; //снова делаем то же самое. По идее, должны получить исходный результат? print $a; //а вот хрен, int32 на выходе 3. Ошибка происходит даже в арифметической операции % (взятие остатка от деления)! Пример: PHP: $a=4294967298; //снова int64 $a=$a % 10; //должны получить 8 - это остаток от деления $a на 10 print $a; //получили 2.. снова та же история, потеряны старшие разряды, мда 4. Советую также не сравнивать большие числа, если требуется большая точность. Скажем, PHP посчитает числа 18446744073709551610 и 18446744073709551619 равными, так как у него в памяти они представлены в виде 1.84467440737E+19 оба. Не ошибались в размерностях только операторы +, -, *, /, ++, --. Не производите сложных вычислений на PHP, особенно с большими числами, это может закончиться плачевно. PS. Если что по операциям непонятно, или если вас бинарный вид числа пугает - спрашивайте, поясню. Perl кстати ошибался меньше, но в побитовых операциях, вроде &, всё равно присутствовали ошибки. Будьте с ним тоже осторожнее.
Возможно, я неправильно выражаюсь, немного поправил пост. Я имел в виду, что PHP произвольно меняет знаки числа, как показано в первых примерах. Perl так не делал.
d_x, дело не в пхп. Code: #include <stdio.h> int main() { printf("%d\n", 4294967294 & 4294967295); return 0; } Тут так же получишь -2. Аналогично и с остальными примерами. ps: http://ru.php.net/manual/en/language.operators.bitwise.php
Помимо Win XP x86, тестировал и на Vista x64. Везде ошибки. Тем более, операторы арифметические работают с int64 нормально на x64 и x86, а вот % почему-то опять нет.
d_x К сожалению, вы заблуждаетесь. Разделение int32 и int64 - в ПХП нет. int - может быть 64 разрядным на _некоторых_ системах, но на руках мы опять-таки будем иметь число от -2^63+1 до 2^63-1 (или -2^31+1 до 2^31-1 но никак не ^32) за счет того, что беззнаковых целых в ПХП - нет. Плюс сам ПХП для винды - x86 по дефолту, сделать поддержку 64 бит - поленились. Далее шаманство с числами. Любое число выходящее за пределы PHP_INT_MAX - автоматически приводится к типу float. Возьмем любой пример: PHP: $a=4294967294; echo gettype($a)."<br>\n"; $b = pow(2,32); // Для виндов и прочих 32битов echo gettype($b)."<br>\n"; // И даже так не прокатит, потому что мы уже выходим за пределы целых чисел $c = pow(2,31)-1; echo gettype($c)."<br>\n"; // Но при этом $d = 2147483647; echo gettype($d)."<br>\n"; Итак, раз переменные у нас с плавающей точкой, то и притензий предъявлять не можем: 1) Не получится играться с битовыми операциями, ибо Оператор приводит оба числа к целому типу, а тут уже имеет место то самое 2) Не получится сравнивать малые разряды числа с большим порядком. Под мантиссу выделяется строго определенное число бит, остальные числа теряются за счет погрешности З.Ы. Погрешности компьютерных измерений - достаточно интересная тема, жалко экзамен по этой теме я завалил(
Gifts, спасибо, теперь всё понятно стало. Действительно, из-за float не работают битовые операторы и взятие остатка от деления. Преобразование неявное, поэтому было непонятно. Забавно, что ++ и -- работают с float.
d_x А почему бы им не работать, обычный инкремент на единицу. Да и, например, можно сколько угодно применять $a-- к числу большему ~1E16 (для 32бит) или 1E20 (для 64 бит), оно от этого не изменится З.Ы. в пхп почти все не явно и "как бы упрощая", но на самом деле плодя ошибки, в отличие от питона, например