[StackOverflow] вопросы недели
От: Sinix  
Дата: 25.06.14 05:57
Оценка: 334 (20) +1
1. Красивейший гейзенбаг:
    static void Main(string[] args)
    {
        float f = Sum(0.1f, 0.2f);
        float g = Sum(0.1f, 0.2f);

        //System.Console.WriteLine("f = " + f + " and g = " + g);

        if (f == g)
        {
            System.Console.WriteLine("The sums are equal");
        }
        else
        {
            System.Console.WriteLine("The sums are Not equal");
        }
        Console.ReadKey();
    }

    static float Sum(float a, float b)
    {
        System.Console.WriteLine(a + b);
        return a + b;
    }

При запуске под отладчиком всё ок, при запуске релизной сборки под x86 без отладчика получаем "The sums are Not equal". При попытке подсмотреть раскомментировать Console.WriteLine(), код начинает вести себя по-человечески. Поведение разрешено стандартом (в процессе вычислений значения с плавающей точкой необязательно будут округляться до float), но баг всё равно красивый. Объяснение по ссылке.


2. Ещё один баг, но уже чуть попроще
    static void Main(string[] args)
    {
        double d1 = 0.84551240822557006;
        string s = d1.ToString("R");
        double d2 = double.Parse(s);
        bool s1 = d1 == d2;

        Console.WriteLine(s1); // -> s1 is False
        Console.ReadKey();
    }

Формат "R" в теории должен обеспечивать безопасное преобразование в строку и обратно. По факту, всё работает нормально только для сборок под x86, в сборке рантайма под x64 закралась ошибка, причём со времён .Net2.0. Объяснение.

Первый пример воспроизводится под .Net4 и выше, второй — начиная с .Net 2.0.


От себя:

1. Особую прелесть багам добавляет чехарда с платформами по умолчанию в разных версиях студии.

Начиная с vs 2012 в проектах по умолчанию используется платформа anycpu32bitpreferred, причём в студии она отображается так же, как и вариант по умолчанию в VS2008, Any CPU. В VS 2010 по умолчанию предлагалась платформа x86, но только для exe-сборок, что тоже не добавляет очевидности.

Если нужно работать в нескольких версиях студии, за правильно выставленнной платформой уследить сложно, я сам пару раз попадался на эти грабли. Ещё информация о изменении умолчаний в студии.


2. В тесты для всяких числодробилок однозначно стоит включать проверки и под x86, и под x64.

3. Stackoverflow крут! На оба вопроса ответы были получены за несколько часов

4. На еженедельную рассылку однозначно стоит подписаться. Подписка спрятана чёрти где, вот инструкция.
Re: [StackOverflow] вопросы недели
От: Nuseraro Россия  
Дата: 25.06.14 06:02
Оценка: +1
Работал в 2008 с Mono — вот там таких багов было побольше и посерьёзнее. Микрософт ещё ничего
Homo Guglens
Re[2]: [StackOverflow] вопросы недели
От: Sinix  
Дата: 25.06.14 06:15
Оценка:
Здравствуйте, Nuseraro, Вы писали:

N>Работал в 2008 с Mono — вот там таких багов было побольше и посерьёзнее. Микрософт ещё ничего


Угу. Кстати, тут в процессе обсуждения способов избежать подобных багов нашлась подборка примеров сравнения значений с заданной погрешностью. Добавил себе в закладки.
Re: [StackOverflow] вопросы недели
От: Аноним  
Дата: 25.06.14 07:04
Оценка: +7
Здравствуйте, Sinix, Вы писали:
S>При запуске под отладчиком всё ок, при запуске релизной сборки под x86 без отладчика получаем "The sums are Not equal".

Ведь не первое десятилетие уже пишут — не сравнивайте значения с плавающей точкой напрямую, а только через eps.
Re[2]: [StackOverflow] вопросы недели
От: Sinix  
Дата: 25.06.14 07:27
Оценка: 144 (3) +1
Здравствуйте, Аноним, Вы писали:

А>Ведь не первое десятилетие уже пишут — не сравнивайте значения с плавающей точкой напрямую, а только через eps.

Не, через Single.Epsilon сравнивать не всегда корректно. Вот классическая статья на эту тему, пример оттуда

float expectedResult = 10000;

float result = +10000.000977; // The closest 4-byte float to 10,000 without being 10,000

float diff = fabs(result — expectedResult); // diff is equal to 0.000977

Single.Epsilon у нас ~ 1.401298E-45. Упс

Если интересуют подробности — см или хардкорную классику, или упрощённый вариант. Вот хорошее объяснение границ применимости eps оттуда:

For numbers between 1.0 and 2.0 FLT_EPSILON represents the difference between adjacent floats. For numbers smaller than 1.0 an epsilon of FLT_EPSILON quickly becomes too large, and with small enough numbers FLT_EPSILON may be bigger than the numbers you are comparing!


Если интересуют примеры более-менее корректного сравнения на c# — см вот эту подборку.
Re[2]: [StackOverflow] вопросы недели
От: Sinix  
Дата: 25.06.14 07:49
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Ведь не первое десятилетие уже пишут — не сравнивайте значения с плавающей точкой напрямую, а только через eps.

Тьфу, я выше глупость сморозил

Речь ведь шла о machine epsilon, не о Single.Epsilon, так?
Re[2]: Сравнение с эпсилон
От: Qbit86 Кипр
Дата: 25.06.14 19:07
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>Ведь не первое десятилетие уже пишут — не сравнивайте значения с плавающей точкой напрямую, а только через eps.


Не первое десятилетие в советах про эпсилон забывают предостеречь, что подобное сравнение — нетранзитивно.
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: [StackOverflow] вопросы недели
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 25.06.14 19:49
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Речь ведь шла о machine epsilon, не о Single.Epsilon, так?


А правильно — domain-specific epsilon
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[3]: [StackOverflow] вопросы недели
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.06.14 06:12
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>float diff = fabs(result — expectedResult); // diff is equal to 0.000977

S> Single.Epsilon у нас ~ 1.401298E-45. Упс

S>Если интересуют подробности — см или хардкорную классику, или упрощённый вариант. Вот хорошее объяснение границ применимости eps оттуда:


И по обеим ссылкам нет ни слова про nextafter() и nexttoward() из IEEE754, через которые "эпсилон" для данного конкретного числа находится тривиально и гарантированно.

Впрочем, для дотнета, если верить, их не реализовали.
The God is real, unless declared integer.
Re[4]: [StackOverflow] вопросы недели
От: Sinix  
Дата: 26.06.14 06:25
Оценка:
Здравствуйте, netch80, Вы писали:

N>Впрочем, для дотнета, если верить, их не реализовали.


Угу.
При желании можно найти готовые реализации, например
http://realtimemadness.blogspot.ru/2012/06/nextafter-in-c-without-allocations-of.html
http://stackoverflow.com/a/3877206/318263
http://programmers.stackexchange.com/a/233274 (см HasMinimalDifference),

но какой-нибудь стандартный вариант устроил бы гораздо больше.
Re: [StackOverflow] вопросы недели
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 26.06.14 08:34
Оценка: 19 (2) +4
Здравствуйте, Sinix, Вы писали:

S> if (f == g)


...

S> bool s1 = d1 == d2;


При виде такого кода в мозгу моментально должна загораться красная лампочка.
... << RSDN@Home 1.2.0 alpha 5 rev. 100 on Windows 8 6.2.9200.0>>
AVK Blog
Re[2]: [StackOverflow] вопросы недели
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 28.06.14 15:27
Оценка:
Здравствуйте, Аноним, Вы писали:

S>>При запуске под отладчиком всё ок, при запуске релизной сборки под x86 без отладчика получаем "The sums are Not equal".


А>Ведь не первое десятилетие уже пишут — не сравнивайте значения с плавающей точкой напрямую, а только через eps.


На самом деле во многих случаях таки можно и даже нужно. Более того, нужно знать, когда числа надо сравнивать через eps, а когда без него, и совершенно точно надо знать что такое eps.
Re[2]: [StackOverflow] вопросы недели
От: Sinclair Россия https://github.com/evilguest/
Дата: 30.06.14 05:36
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>При виде такого кода в мозгу моментально должна загораться красная лампочка.

Ага. Лично я уже сразу начинаю подозревать, что в данном сравнении речь идёт о кэшировании — т.е. этот флоат используется в роли чего-то типа ETag, чтобы отслеживать валидность результата вычисления.

Ну, потому что в предыдущих N случаях, когда я видел сравнение флоатов через == и поднимал панику, оказывалось, что авторы кода — неидиоты, и побитовость — намеренная.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: [StackOverflow] вопросы недели
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 01.12.16 18:11
Оценка: 75 (2)
Здравствуйте, Sinix, Вы писали:

S>1. Красивейший гейзенбаг:


Запощу для архива аналог для C.

#include <stdio.h>
#include <ieeefp.h>

volatile float a, b, f1;
int main() {
  fpsetprec(FP_PD);
  a = 0.1f;
  b = 0.2f;
  f1 = a + b;
  printf("%d\n", f1 == (a + b));
  return 0;
}


fpsetprec это для FreeBSD, для других платформ _controlfp и тому подобное. FP_PS ставит точность на single, ответ — 1. FP_PD (double), FP_PE (extended) приводит к ответу 0. Разумеется, компиляция должна быть с использованием FPU (умолчание для 32 бит), на SSE не повторяется. От уровня отладки не зависит, благодаря volatile.

В ассемблере получается

 80485c8:       d9 05 b8 98 04 08       flds   a
 80485ce:       d9 05 c0 98 04 08       flds   b
 80485d4:       de c1                   faddp  %st,%st(1)
 80485d6:       d9 1d bc 98 04 08       fstps  f1
 80485dc:       d9 05 bc 98 04 08       flds   f1
 80485e2:       d9 05 b8 98 04 08       flds   a
 80485e8:       d9 05 c0 98 04 08       flds   b
 80485ee:       de c1                   faddp  %st,%st(1)
 80485f0:       da e9                   fucompp


и за счёт сравнения на стеке FPU при более широкой точности — получается данный эффект.

В стандарте C возможность подобного определяется макросом FLT_EVAL_METHOD, но в этих тестах он был равен -1, что значит, что нет определённости на этапе компиляции. Для 64-битного режима с использованием SSE он равен 0.
В дотнете, как я понял, спросить это нет возможности.
The God is real, unless declared integer.
floating point
Re: [StackOverflow] вопросы недели
От: pva  
Дата: 03.12.16 10:32
Оценка:
Здравствуйте, Sinix, Вы писали:

Не вопрос недели, но вот нарвался на пренеприятнейшую "багу" (?)
(float)(16781433u) 16781432 float
(float)(16781434u) 16781434 float

Может кто просветит почему 16781433 не представимо в float?
newbie
Re[2]: [StackOverflow] вопросы недели
От: Sinix  
Дата: 03.12.16 10:37
Оценка: 2 (1)
Здравствуйте, pva, Вы писали:

pva>Может кто просветит почему 16781433 не представимо в float?

Significand or mantissa 0-22

(c)
2^22 == 4 194 304
Re[2]: [StackOverflow] вопросы недели
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.12.16 10:41
Оценка: 4 (1)
Здравствуйте, pva, Вы писали:

pva>Здравствуйте, Sinix, Вы писали:


pva>Не вопрос недели, но вот нарвался на пренеприятнейшую "багу" (?)

pva> (float)(16781433u) 16781432 float
pva> (float)(16781434u) 16781434 float

pva>Может кто просветит почему 16781433 не представимо в float?


У float только 24 точных двоичных разряда.

Число 16781433 == 0x1001079 == 0b1_0000_0000_0001_0000_0111_1001 — 25 двоичных разрядов.
Соответственно, оно округляется до одного из ближайших, в вашем случае
0b1_0000_0000_0001_0000_0111_1000 == 16781432, но могло быть округлено и до
0b1_0000_0000_0001_0000_0111_1010 == 16781434.

В диапазоне от 8388608 до 16777216 шаг значений float равен 1.
В диапазоне от 16777216 до 33554432 он уже равен 2.

Переходите на double или на Decimal, если нужна бо́льшая точность.
The God is real, unless declared integer.
Re[3]: [StackOverflow] вопросы недели
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.12.16 10:44
Оценка: 46 (1) :)
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, pva, Вы писали:


pva>>Может кто просветит почему 16781433 не представимо в float?

S>

S>Significand or mantissa 0-22

S>(c)
S>2^22 == 4 194 304

У тебя сработал off-by-one, аж два раза

Минимальное из непредставимых во float положительных целых равно 2^24+1.
The God is real, unless declared integer.
Re[3]: [StackOverflow] вопросы недели
От: pva  
Дата: 03.12.16 11:03
Оценка:
Здравствуйте, netch80, Вы писали:

pva>>Может кто просветит почему 16781433 не представимо в float?

N>У float только 24 точных двоичных разряда.
Да, я в принципе догадался что проблема в представлении. Просто был немного расстроен подобным косяком. )

N>Переходите на double или на Decimal, если нужна бо́льшая точность.

Пока запатчил просто инкрементом на следующее представимое число. Но вероятно прийдется уйти на дабл.
newbie
Re: [StackOverflow] вопросы недели
От: Gattaka Россия  
Дата: 03.12.16 15:28
Оценка: :))
Здравствуйте, Sinix, Вы писали:

S>1. Красивейший гейзенбаг:

S>
S>    static void Main(string[] args)
S>    {
S>        float f = Sum(0.1f, 0.2f);
S>        float g = Sum(0.1f, 0.2f);

S>        //System.Console.WriteLine("f = " + f + " and g = " + g);

S>        if (f == g)
S>        {
S>            System.Console.WriteLine("The sums are equal");
S>        }
S>        else
S>        {
S>            System.Console.WriteLine("The sums are Not equal");
S>        }
S>        Console.ReadKey();
S>    }

S>    static float Sum(float a, float b)
S>    {
S>        System.Console.WriteLine(a + b);
S>        return a + b;
S>    }
S>

Переодически смотрю что Sinix пишет, раз в два месяца уже раз пять он вский раз с изумлением и радостью ловит один и тот же гейзенбаг с float
Re[2]: [StackOverflow] вопросы недели
От: _NN_ www.nemerleweb.com
Дата: 03.12.16 19:30
Оценка:
Здравствуйте, pva, Вы писали:

pva>Может кто просветит почему 16781433 не представимо в float?

Можно поинтересоваться почему у вас float, а не double? Оптимизация по размеру или просто так было ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[3]: [StackOverflow] вопросы недели
От: pva  
Дата: 03.12.16 20:34
Оценка:
Здравствуйте, _NN_, Вы писали:

pva>>Может кто просветит почему 16781433 не представимо в float?

_NN>Можно поинтересоваться почему у вас float, а не double? Оптимизация по размеру или просто так было ?
Можно. Копипастил реализацию интерполяции из открытых источников.
newbie
Re: [StackOverflow] вопросы недели
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 01.09.23 05:21
Оценка:
Здравствуйте, Sinix, Вы писали:

Кросс-линк на свежее на хабре.
The God is real, unless declared integer.
Re[2]: [StackOverflow] вопросы недели
От: IT Россия linq2db.com
Дата: 25.09.23 13:59
Оценка:
Здравствуйте, AndrewVK, Вы писали:

S>> if (f == g)

AVK>При виде такого кода в мозгу моментально должна загораться красная лампочка.

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