[PHP] Сравнение одинаковых чисел не работает?

Вопрос к знатокам. Скорее всего я, что-то упускаю из виду…

$var1 = floatval(995); // double 995
$var2 = floatval(19.9 * 50); // double 995
$var3 = floatval(1990 - floatval(19.9 * 50)); // double 995

$format = '%0' . "b\n";
printf($format, $var1); // 1111100011
printf($format, $var2); // 1111100010
printf($format, $var3); // 1111100011

print PHP_EOL . '===' . PHP_EOL;
print intval($var1 === $var2) . PHP_EOL; // false
print intval($var1 === $var3) . PHP_EOL; // false
print intval($var2 === $var3) . PHP_EOL; // false

print PHP_EOL . 'intval === intval' . PHP_EOL;
print intval(intval($var1) === intval($var2)) . PHP_EOL; // false
print intval(intval($var1) === intval($var3)) . PHP_EOL; // true
print intval(intval($var2) === intval($var3)) . PHP_EOL; // false

print PHP_EOL . 'round === round' . PHP_EOL;
print intval(round($var1, 2) === round($var2, 2)) . PHP_EOL; // true
print intval(round($var1, 2) === round($var3, 2)) . PHP_EOL; // true
print intval(round($var2, 2) === round($var3, 2)) . PHP_EOL; // true

Под катом вопросы…

Вопросы:
  1. Почему $var1 и $var3 в битах 1111100011, а $var2 — 1111100010?
  2. Почему $var1 и $var3 не равны (ни тождественно, ни обычно)?
  3. Почему только после обработки round-ом все 3 числа становятся равны?
Тестировалось на PHP 7. Вполне вероятно, что и в нижних версиях тоже самое.
Павел Гвоздь
20 декабря 2016, 11:11
modx.pro
2 986
+1
Поблагодарить автора Отправить деньги

Комментарии: 17

Павел Гвоздь
20 декабря 2016, 17:14
+1
Неужели настолько незаметно?)
    Алексей Ерохин
    20 декабря 2016, 18:11
    0
    php.net/manual/ru/language.types.float.php

    Неужели настолько незаметно?
      Павел Гвоздь
      20 декабря 2016, 18:17
      0
      О чём речь? О памятке «Точность чисел с плавающей точкой»? Почему тогда:
      print PHP_EOL . 'intval === intval' . PHP_EOL;
      print intval(intval($var1) === intval($var2)) . PHP_EOL; // false
      print intval(intval($var1) === intval($var3)) . PHP_EOL; // true
      print intval(intval($var2) === intval($var3)) . PHP_EOL; // false
      ?
        Алексей Ерохин
        20 декабря 2016, 21:53
        +2
        php.net/manual/ru/language.types.float.php
        Кроме того, рациональные числа, которые могут быть точно представлены в виде чисел с плавающей точкой с основанием 10, например, 0.1 или 0.7, не имеют точного внутреннего представления в качестве чисел с плавающей точкой с основанием 2, вне зависимости от размера мантиссы. Поэтому они и не могут быть преобразованы в их внутреннюю двоичную форму без небольшой потери точности. Это может привести к неожиданным результатам: например, floor((0.1+0.7)*10) скорее всего вернет 7 вместо ожидаемого 8, так как результат внутреннего представления будет чем-то вроде 7.9999999999999991118....
        php.net/manual/ru/language.types.integer.php#language.types.integer.casting
        При преобразовании из float в integer, число будет округлено в сторону нуля.
        floatval(19.9 * 50) по факту чуть меньше чем 995, а floatval(1990 — floatval(19.9 * 50)) чуть больше (в десятичном представлении), при intval число округляется в строну 0, поэтому var2 становится 994,var3 = 995, отсюда и результаты
        print intval($var1) > intval($var2); //true
Konstantin
20 декабря 2016, 19:17
0
а так если?

$var1 = (float) 995;
$var2 = (float) bcmul(19.9, 50);
    Павел Гвоздь
    20 декабря 2016, 19:21
    +1
    Не проверял. Меня не устраивает, что необходимо использовать стороннюю библиотеку для деления числа на число, учитывая то, что я делаю продукт для массового использования… Да и проблему для себя я решил, однако хочется понять, почему такое происходит.
      Konstantin
      20 декабря 2016, 19:29
      0
      да оно вроде давно встроено в пых еще с PHP 5.0.0. вроде
        Павел Гвоздь
        20 декабря 2016, 19:30
        0
        Странно, а у меня нет…
          Алексей Ерохин
          20 декабря 2016, 21:56
          0
          Начиная с PHP 4.0.4, libbcmath встроена в PHP. Расширение не требует внешних библиотек.
            Павел Гвоздь
            20 декабря 2016, 22:11
            0
            Угу, знаю теперь… а у меня чёт он не поддерживается…
      Konstantin
      20 декабря 2016, 19:20
      0
      (float) быстрее в 6 раз, чем floatval()

      вроде бы
        Павел Гвоздь
        20 декабря 2016, 19:22
        0
        Не в этом суть. Floatval использовал в примере лишь для наглядности.
        Иван Климчук
        20 декабря 2016, 21:49
        +2
        Кроме того, рациональные числа, которые могут быть точно представлены в виде чисел с плавающей точкой с основанием 10, например, 0.1 или 0.7, не имеют точного внутреннего представления в качестве чисел с плавающей точкой с основанием 2, вне зависимости от размера мантиссы. Поэтому они и не могут быть преобразованы в их внутреннюю двоичную форму без небольшой потери точности. Это может привести к неожиданным результатам: например, floor((0.1+0.7)*10) скорее всего вернет 7 вместо ожидаемого 8, так как результат внутреннего представления будет чем-то вроде 7.9999999999999991118....

        На деле так оно и есть.

        php > printf('%d', 19.9*50);
        994
        php > printf('%f', 19.9*50);
        995.000000
          Иван Климчук
          20 декабря 2016, 21:51
          +2
          Пример из документации такой же:

          php > printf('%d', (0.1+0.7) * 10);
          7
          php > printf('%f', (0.1+0.7) * 10);
          8.000000
          Как решение, и так мы и делали, когда писали биллинг, это всегда приводить правильно к типу, т.е. работать всегда явно с float.
        Роман Садоян
        20 декабря 2016, 23:04
        0
        Обсуждали как-то похожее в чате СПб Фронтэнд, возможно будет интересна эта статья habrahabr.ru/post/112953/
          Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
          17