Ошибки PHP - знаки чисел, int32 и int64

Discussion in 'PHP' started by d_x, 1 Sep 2009.

  1. d_x

    d_x Banned

    Joined:
    25 Mar 2008
    Messages:
    558
    Likes Received:
    650
    Reputations:
    210
    В этой небольшой статейке расскажу о некоторых неприятных ошибках (багофичах) 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 кстати ошибался меньше, но в побитовых операциях, вроде &, всё равно присутствовали ошибки. Будьте с ним тоже осторожнее.
     
    #1 d_x, 1 Sep 2009
    Last edited: 1 Sep 2009
    4 people like this.
  2. astrologer

    astrologer Elder - Старейшина

    Joined:
    30 Aug 2007
    Messages:
    837
    Likes Received:
    267
    Reputations:
    59
    Особенно если учесть то, что беззнаковых в php нет совсем :)
     
    2 people like this.
  3. d_x

    d_x Banned

    Joined:
    25 Mar 2008
    Messages:
    558
    Likes Received:
    650
    Reputations:
    210
    Возможно, я неправильно выражаюсь, немного поправил пост. Я имел в виду, что PHP произвольно меняет знаки числа, как показано в первых примерах. Perl так не делал.
     
    #3 d_x, 1 Sep 2009
    Last edited: 1 Sep 2009
  4. oRb

    oRb Elder - Старейшина

    Joined:
    9 May 2008
    Messages:
    294
    Likes Received:
    582
    Reputations:
    256
    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
     
  5. d_x

    d_x Banned

    Joined:
    25 Mar 2008
    Messages:
    558
    Likes Received:
    650
    Reputations:
    210
    Помимо Win XP x86, тестировал и на Vista x64. Везде ошибки. Тем более, операторы арифметические работают с int64 нормально на x64 и x86, а вот % почему-то опять нет.
     
  6. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    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) Не получится сравнивать малые разряды числа с большим порядком. Под мантиссу выделяется строго определенное число бит, остальные числа теряются за счет погрешности

    З.Ы. Погрешности компьютерных измерений - достаточно интересная тема, жалко экзамен по этой теме я завалил(
     
    _________________________
    #6 Gifts, 1 Sep 2009
    Last edited: 1 Sep 2009
    1 person likes this.
  7. d_x

    d_x Banned

    Joined:
    25 Mar 2008
    Messages:
    558
    Likes Received:
    650
    Reputations:
    210
    Gifts, спасибо, теперь всё понятно стало. Действительно, из-за float не работают битовые операторы и взятие остатка от деления. Преобразование неявное, поэтому было непонятно. Забавно, что ++ и -- работают с float.
     
  8. Gifts

    Gifts Green member

    Joined:
    25 Apr 2008
    Messages:
    2,494
    Likes Received:
    807
    Reputations:
    614
    d_x А почему бы им не работать, обычный инкремент на единицу. Да и, например, можно сколько угодно применять $a-- к числу большему ~1E16 (для 32бит) или 1E20 (для 64 бит), оно от этого не изменится

    З.Ы. в пхп почти все не явно и "как бы упрощая", но на самом деле плодя ошибки, в отличие от питона, например :p
     
    _________________________
    #8 Gifts, 1 Sep 2009
    Last edited: 1 Sep 2009