Модераторы: Partizan, gambit

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> WTF? Magic??? странные грабли с Equals 
:(
    Опции темы
Partizan
Дата 6.10.2008, 16:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 18
Всего: 67



Привет, All =)

в общем вот есть код с комментариями:
Код

    class Program

    {

        // первоначальная версия структуры

        public struct D1

        {

            public double d;

            public int f;

        }

 

        // в результате улучшения кода

        // в струтктуре поле f из int стала double

        public struct D2 

        {

            public double d;

            public double f;

        }

 

        static void Main(string[] args)

        {

            // сценарий работы со структурой

            D1 a = new D1();

            D1 b = new D1();

            a.f = b.f = 1;

            a.d = 0.0;

            b.d = -0.0;

            bool r1 = a.Equals(b); // true, все работает правильно

 

            // тот же сценарий, но с новой версией структуры

            D2 c = new D2();

            D2 d = new D2();

            c.f = d.f = 1;

            c.d = 0.0;

            d.d = -0.0;

            bool r2 = c.Equals(d); // false, упс! совсем другой результат

        }

    }



Кто что об этом думает?  smile 


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
archeg
Дата 6.10.2008, 23:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 612
Регистрация: 6.1.2007
Где: Киев

Репутация: 11
Всего: 27



Интерестный вопрос. Вот код класса ValueType (род. класс структуры):
Код

    [Serializable]
[System.Runtime.InteropServices.ComVisible(true)]
    public abstract class ValueType {
 
        public override bool Equals (Object obj) {
            BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)"); 
            if (null==obj) { 
                return false;
            } 
            RuntimeType thisType = (RuntimeType)this.GetType();
            RuntimeType thatType = (RuntimeType)obj.GetType();

            if (thatType!=thisType) { 
                return false;
            } 
 
            Object thisObj = (Object)this;
            Object thisResult, thatResult; 

            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp
            if (CanCompareBits(this)) 
                return FastEqualsCheck(thisObj, obj);
 
            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 

            for (int i=0; i<thisFields.Length; i++) { 
                thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);

                if (thisResult == null) { 
                    if (thatResult != null)
                        return false; 
                } 
                else
                if (!thisResult.Equals(thatResult)) { 
                    return false;
                }
            }
 
            return true;
        } 
 
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern bool CanCompareBits(Object obj); 

        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern bool FastEqualsCheck(Object a, Object b);
 


Прошелся дебаггером - фолс возвращает в месте где вызываеться CanCompareBits а затем FastEqualsCheck. К несчастью оба метода имеют анменейдж реализацию, и я неуверен что они делают - мое предположоние, судя по названию, просто сравнивают биты структур в памяти.

Если руководиться этим выходит что при double a = 0.0; и double b = -0.0; а и b имеют одинаковое значение, но не одинаковые внутренние поля (например, кажись там есть поле _isNegative или что-то подобное) - а поэтому сравнивание происходит не так как бы хотелось.

Надеюсь что прав. Если есть мастера у которых другой ответ - образумьте  smile 



--------------------
ИМХО задница есть универсальный интерфейс. Ибо через задницу можно сделать абсолютно ВСЕ (bash.org.ru)

Дядька всегда можно спросить в аське, если не задалбывать - не откажет smile
И вообще, на самом деле я студент, и ненавижу обращение на "Вы") Тут все свои  ;)
PM MAIL ICQ Jabber   Вверх
jonie
Дата 6.10.2008, 23:32 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



на самом деле хуже то что true выдается...
вообще он и не должен как раз появляться, числа 0.0 и -0.0 это разные числа... подробне можно найти в IEEE чета там, про машинное представление числа в общем....

Код

    class Program
    {

        // первоначальная версия структуры

        public struct D1
        {

            public double d;

            public int f;

        }



        // в результате улучшения кода

        // в струтктуре поле f из int стала double

        public struct D2
        {

            public double d;

            public double f;

        }


        unsafe static void hexDump(byte* p, int len)
        {
            for (int i=0; i<len; ++i )
            {
                Console.Write("{0:X} ", *(p+i));
            }
        }

        static void Main(string[] args)
        {

            // сценарий работы со структурой

            D1 a = new D1();

            D1 b = new D1();

            a.f = b.f = 1;

            a.d = 0.0f;

            b.d = -0.0f;

            bool r1 = a.Equals(b); // true, все работает правильно
            unsafe
            {
                byte* p = (byte*)&a;
                hexDump(p, sizeof(D1));
                p = (byte*)&b;
                Console.WriteLine();
                hexDump(p, sizeof(D1));
            }


            // тот же сценарий, но с новой версией структуры

            D2 c = new D2();

            D2 d = new D2();

            c.f = d.f = 1;

            c.d = 0.0f;

            d.d = -0.0f;

            bool r2 = c.Equals(d); // false, упс! совсем другой результат
            Console.WriteLine();
            Console.WriteLine("-------------");
            unsafe
            {
                byte* p = (byte*)&c;
                hexDump(p, sizeof(D2));
                Console.WriteLine();
                p = (byte*)&d;
                hexDump(p, sizeof(D2));
            }

        }

    }



--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
archeg
Дата 7.10.2008, 01:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 612
Регистрация: 6.1.2007
Где: Киев

Репутация: 11
Всего: 27



Цитата(jonie @ 6.10.2008,  23:32)
на самом деле хуже то что true выдается...
вообще он и не должен как раз появляться, числа 0.0 и -0.0 это разные числа... подробне можно найти в IEEE чета там, про машинное представление числа в общем....

Код

    class Program
    {

        // первоначальная версия структуры

        public struct D1
        {

            public double d;

            public int f;

        }



        // в результате улучшения кода

        // в струтктуре поле f из int стала double

        public struct D2
        {

            public double d;

            public double f;

        }


        unsafe static void hexDump(byte* p, int len)
        {
            for (int i=0; i<len; ++i )
            {
                Console.Write("{0:X} ", *(p+i));
            }
        }

        static void Main(string[] args)
        {

            // сценарий работы со структурой

            D1 a = new D1();

            D1 b = new D1();

            a.f = b.f = 1;

            a.d = 0.0f;

            b.d = -0.0f;

            bool r1 = a.Equals(b); // true, все работает правильно
            unsafe
            {
                byte* p = (byte*)&a;
                hexDump(p, sizeof(D1));
                p = (byte*)&b;
                Console.WriteLine();
                hexDump(p, sizeof(D1));
            }


            // тот же сценарий, но с новой версией структуры

            D2 c = new D2();

            D2 d = new D2();

            c.f = d.f = 1;

            c.d = 0.0f;

            d.d = -0.0f;

            bool r2 = c.Equals(d); // false, упс! совсем другой результат
            Console.WriteLine();
            Console.WriteLine("-------------");
            unsafe
            {
                byte* p = (byte*)&c;
                hexDump(p, sizeof(D2));
                Console.WriteLine();
                p = (byte*)&d;
                hexDump(p, sizeof(D2));
            }

        }

    }

Точно. Как-то в эту сторону не подумал smile
Спецификация double (IEEE 754)
Только видать .нет при обработке Equals игнорирует разницу, ибо:
Код

            double aa = 0.0;
            double bb = -0.0;
            Console.WriteLine(aa.Equals(bb));

Выдаст true ;)
Что в общем правильно. Только в памяти то они разные.
-0 = 1 11111111 00000000000000000000000
0 = 0 11111111 00000000000000000000000

Поэтому во-втором случае еквалс таких структур возвращает false.
В первом случае, оно почему-то решает что существуют ссылки из GC в структуре и поэтому отказываеться от сравнения по битам, а сравнивает по полям - что призводит к ответу в true


--------------------
ИМХО задница есть универсальный интерфейс. Ибо через задницу можно сделать абсолютно ВСЕ (bash.org.ru)

Дядька всегда можно спросить в аське, если не задалбывать - не откажет smile
И вообще, на самом деле я студент, и ненавижу обращение на "Вы") Тут все свои  ;)
PM MAIL ICQ Jabber   Вверх
nikitao
Дата 7.10.2008, 02:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Кот-программист
***


Профиль
Группа: Завсегдатай
Сообщений: 1206
Регистрация: 30.8.2005
Где: Спб

Репутация: 4
Всего: 26



идиотизм. от такого бага можно в психушку попасть , прежде чем поймешь в чем дело.


--------------------
Жизнь - печальная штука.
PM MAIL ICQ Skype GTalk   Вверх
HalkaR
Дата 7.10.2008, 10:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Пуфыстый назгул
****


Профиль
Группа: Экс. модератор
Сообщений: 2132
Регистрация: 8.12.2002
Где: В Москве

Репутация: 14
Всего: 42



Цитата(archeg @  6.10.2008,  23:11 Найти цитируемый пост)
(например, кажись там есть поле _isNegative или что-то подобное) 
Тоже так думал, но в реализации double такого нет. Разве что есть ведущий флаг отвечающий за отрицательность числа - но он есть и у int.

PM MAIL   Вверх
Partizan
Дата 7.10.2008, 10:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 18
Всего: 67



Итак, есть люди, которые могут до конца прояснить логику такого неоднозначного поведения?  smile 


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
HalkaR
Дата 7.10.2008, 10:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Пуфыстый назгул
****


Профиль
Группа: Экс. модератор
Сообщений: 2132
Регистрация: 8.12.2002
Где: В Москве

Репутация: 14
Всего: 42



Цитата(Partizan @  7.10.2008,  10:27 Найти цитируемый пост)
Итак, есть люди, которые могут до конца прояснить логику такого неоднозначного поведения? 
Есть они работают в MS smile

Вот - нашел - всетаки в стандарте это есть:

C# supports two floating-point types: float and double. The float and double types are represented
using the 32-bit single-precision and 64-bit double-precision IEC 60559 formats, which provide the
following sets of values:
• Positive zero and negative zero. In most situations, positive zero and negative zero behave identically as
the simple value zero, but certain operations distinguish between the two (§14.7.2).

• Positive infinity and negative infinity. Infinities are produced by such operations as dividing a non-zero
number by zero. [Example: 1.0 / 0.0 yields positive infinity, and –1.0 / 0.0 yields negative infinity.
end example]
• The Not-a-Number value, often abbreviated NaN. NaNs are produced by invalid floating-point
operations, such as dividing zero by zero.
• The finite set of non-zero values of the form s × m × 2e, where s is 1 or −1, and m and e are determined
by the particular floating-point type: For float, 0 < m < 224 and −149 ≤ e ≤ 104, and for double,
0 < m < 253 and −1075 ≤ e ≤ 970. Denormalized floating-point numbers are considered valid non-zero
values. C# neither requires nor forbids that a conforming implementation support denormalized floatingpoint
numbers.


Т.е. для float и для double есть константные значение +0 и -0.

Добавлено через 2 минуты и 35 секунд
Стандарт всетаки великая вещь, я вчера пытался в нем выискать особенности работы Equals для double - ничего не нашел, подумал что это какая-то недокументированная особенность. А сегодня посмотрел описалово double - в первой же строке объяснение smile
PM MAIL   Вверх
Partizan
Дата 7.10.2008, 11:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 18
Всего: 67



HalkaR, ненененене )
мне не понятна другая вещь)

почему в первом случае 0.0 и -0.0 при сравнении структур воспринимаются как равные,
а во втором - как отличные друг от друга?

ведь по сути операция - одна и та же... вызывается Equals для ValueType...


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
archeg
Дата 7.10.2008, 11:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 612
Регистрация: 6.1.2007
Где: Киев

Репутация: 11
Всего: 27



ОК. Где бы взять код анменейдж методов CanCompareBits и FastEqualsCheck ? smile
Проблема именно в них. Все другое работает правильно
Цитата

Разве что есть ведущий флаг отвечающий за отрицательность числа - но он есть и у int.

Инт не отличает -0 от 0. В памяти и то и то выглядит как 000000....
Походу -0 и 0 есть только в double и float

Это сообщение отредактировал(а) archeg - 7.10.2008, 11:56


--------------------
ИМХО задница есть универсальный интерфейс. Ибо через задницу можно сделать абсолютно ВСЕ (bash.org.ru)

Дядька всегда можно спросить в аське, если не задалбывать - не откажет smile
И вообще, на самом деле я студент, и ненавижу обращение на "Вы") Тут все свои  ;)
PM MAIL ICQ Jabber   Вверх
HalkaR
Дата 7.10.2008, 12:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Пуфыстый назгул
****


Профиль
Группа: Экс. модератор
Сообщений: 2132
Регистрация: 8.12.2002
Где: В Москве

Репутация: 14
Всего: 42



Цитата(Partizan @  7.10.2008,  11:29 Найти цитируемый пост)
почему в первом случае 0.0 и -0.0 при сравнении структур воспринимаются как равные,
Потому что double хранится в памяти совершенно извратно и для -0 у него есть специальная константа далеко не равная 0. Если интересно - она равна - BitConverter.Int64BitsToDouble(-9223372036854775808L); У int все проще - у него нет таких констант.

PM MAIL   Вверх
archeg
Дата 7.10.2008, 12:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 612
Регистрация: 6.1.2007
Где: Киев

Репутация: 11
Всего: 27



Цитата(HalkaR @ 7.10.2008,  12:24)
Цитата(Partizan @  7.10.2008,  11:29 Найти цитируемый пост)
почему в первом случае 0.0 и -0.0 при сравнении структур воспринимаются как равные,
Потому что double хранится в памяти совершенно извратно и для -0 у него есть специальная константа далеко не равная 0. Если интересно - она равна - BitConverter.Int64BitsToDouble(-9223372036854775808L); У int все проще - у него нет таких констант.

Если бы все было так просто, тогда почему же в 1-м случае результат true? В первом те же даблы с +0 и -0


--------------------
ИМХО задница есть универсальный интерфейс. Ибо через задницу можно сделать абсолютно ВСЕ (bash.org.ru)

Дядька всегда можно спросить в аське, если не задалбывать - не откажет smile
И вообще, на самом деле я студент, и ненавижу обращение на "Вы") Тут все свои  ;)
PM MAIL ICQ Jabber   Вверх
Partizan
Дата 7.10.2008, 12:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 18
Всего: 67



HalkaR, а какая разница? переменные, хранящие 0.0 и -0.0 в обоих структурах (и D1, и D2) имеют тип double  smile 


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
HalkaR
Дата 7.10.2008, 13:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Пуфыстый назгул
****


Профиль
Группа: Экс. модератор
Сообщений: 2132
Регистрация: 8.12.2002
Где: В Москве

Репутация: 14
Всего: 42



Partizan, еще раз повторяю - это для тебя -0 и +0 это одно и тоже. Для типа double в C# -0 - это специальная константа, которая хранится в памяти не как 0.

Добавлено через 42 секунды
То, что при отладке студия показывает значение это константы как 0 - ничего не значит - во внутреннем представлении double  - это не тоже самое что 0.

Добавлено через 1 минуту и 47 секунд
Цитата(archeg @  7.10.2008,  12:27 Найти цитируемый пост)
Если бы все было так просто, тогда почему же в 1-м случае результат true? В первом те же даблы с +0 и -0 

Мда.. тут пока загадка.

Добавлено через 2 минуты и 5 секунд
Цитата(Partizan @  7.10.2008,  12:28 Найти цитируемый пост)
HalkaR, а какая разница? переменные, хранящие 0.0 и -0.0 в обоих структурах (и D1, и D2) имеют тип double  smile  

Хм, еще непонятно.
PM MAIL   Вверх
Partizan
Дата 7.10.2008, 13:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Let's do some .NET
****


Профиль
Группа: Модератор
Сообщений: 2828
Регистрация: 19.12.2005
Где: Санкт-Петербург

Репутация: 18
Всего: 67



HalkaR, да для меня +0.0 и -0.0 - это тоже разные вещи....я просто не понимаю, почему в одном случае Equals их воспринимает как одинаковые, а в другом - нет...

Добавлено через 1 минуту и 20 секунд
кстати разведка доложила, что жаба в обоих случаях возвращает false.


--------------------
СУВ,
       Partizan.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
mr.DUDA
THandle

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »


 




[ Время генерации скрипта: 0.0660 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.