locked
Как такое возможно? RRS feed

  • Вопрос

  • Возникла проблема в большой программе, написал маленькое консольное приложение чтобы проверить возникшую проблему. Кто-нибудь может объяснить полученные результаты в VisualStudio?

    Module Module1
    
        Sub Main()
            Console.InputEncoding = Text.Encoding.UTF8
            Console.OutputEncoding = Text.Encoding.UTF8
    
            Dim _maxP As Single = 1.6F
            Dim _selectedP As Integer = 16
    
            If (_maxP * 10 > _selectedP) Then
                Console.WriteLine("Значения не равны.")
            Else
                Console.WriteLine("Значения равны.")
            End If
    
            Console.ReadLine()
        End Sub
    
    End Module

    В Visual Studio добавил точку останова на проверке условия и получаю следующее:

    и какой текст по Вашему должен отобразиться если проверка условия 

    (_maxP * 10 > _selectedP)

    выдает True, но выводится вот такое сообщение, почему?


    • Изменено SergejS 4 мая 2021 г. 9:10
    4 мая 2021 г. 8:49

Ответы

  • Нашёл решение, нужно преобразовывать _maxP * 10 к типу Decimal, тогда выбор происходит правильный. 

    If (CDec(_maxP * 10) > _selectedP) Then
        Console.WriteLine("Значения не равны.")
    Else
        Console.WriteLine("Значения равны.")
    End If

    Или кто то может ещё что-то предложить?

    • Изменено SergejS 4 мая 2021 г. 13:25
    • Помечено в качестве ответа SergejS 4 мая 2021 г. 20:25
    4 мая 2021 г. 13:23

Все ответы

  • Нашёл решение, нужно преобразовывать _maxP * 10 к типу Decimal, тогда выбор происходит правильный. 

    If (CDec(_maxP * 10) > _selectedP) Then
        Console.WriteLine("Значения не равны.")
    Else
        Console.WriteLine("Значения равны.")
    End If

    Или кто то может ещё что-то предложить?

    • Изменено SergejS 4 мая 2021 г. 13:25
    • Помечено в качестве ответа SergejS 4 мая 2021 г. 20:25
    4 мая 2021 г. 13:23
  • Нашёл решение, нужно преобразовывать _maxP * 10 к типу Decimal, тогда выбор происходит правильный. 

    If (CDec(_maxP * 10) > _selectedP) Then
        Console.WriteLine("Значения не равны.")
    Else
        Console.WriteLine("Значения равны.")
    End If

    Или кто то может ещё что-то предложить?

    Это фундаментальное свойство чисел с плавающей запятой о котором должен знать каждый работающий с компьютером, в том числе и не программисты. :)

    Но, увы, не знают, иногда даже программисты со стажем. После чего возникают "проблемы" как у вас, заявления что "Эксель неправильно считает" и т.п.

    На деле все просто: вещественных чисел бесконечное количество в любом конечном интервале, а значений в 64 или 32 битных форматах с плавающей запятой менее чем 2^64/2^32, то есть конечное число. Очевидно что бесконечное количество чисел не могут быть представлены в формате с плавающей запятой так как оно имеет конечное число значений, поэтому очень часто вместо конкретного числа используется ближайшее представимое значение.

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

    Интересно что в начале 90х когда я был студентом (причем не по компьютерной специальности), об этих (и других - их не мало) особенностях рассказывали в курсе по вычислительной технике. Очевидно сейчас это тайна. :)



    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 мая 2021 г. 16:15
    Модератор
  • о том что не все десятичные числа можно точно представить, я прекрасно знаю! Я проверяю не на равенство! А проблема в том, что проверяемое условие дает True в качестве результата, но при этом выполнятся блок, который должен выполнятся при False!
    4 мая 2021 г. 17:54
  • >Интересно что в начале 90х когда я был студентом (причем не по компьютерной специальности), об этих (и других - их не мало) особенностях рассказывали в курсе по вычислительной технике. Очевидно сейчас это тайна. :)

    Изучается и сейчас, даже в средней школе на информатике, насколько я помню. Проблема в том, что само знание представления с плавающей точкой еще не дает навыка избегать подобных багов, это приходит только с опытом реального применения их к расчетам, причем баги эти могут быть самыми разными. Я например правило "сравнивать float нужно с точностью до дельты" усвоил сразу, но все равно срезался на том, что пытался в цикле делать x+=0.01 и ожидал, что результат будет кратен 0.01. Я не думаю, что можно требовать знать это от всех пользователей, работающих с компьютером. Более реальное решение в том, чтобы эти проблемы до пользователей не доходили. Стандартные float и double быстрые, но неудобные для всего, кроме научных расчетов. Использование decimal, где это возможно, будет хорошим шагом вперед. В Excel, кстати, 1.6 * 10 равно 16 с точностью до 30 знаков после запятой, видимо, у него своя система вещественных чисел. 

    >Я проверяю не на равенство! А проблема в том, что проверяемое условие дает True в качестве результата, но при этом выполнятся блок, который должен выполнятся при False!

    Отладчик использует для вычисления выражений свой интерпретатор IL, который может иметь другие флаги параметров float по сравнению с живым процессом с CLR.

    4 мая 2021 г. 18:03
  • о том что не все десятичные числа можно точно представить, я прекрасно знаю! Я проверяю не на равенство! А проблема в том, что проверяемое условие дает True в качестве результата, но при этом выполнятся блок, который должен выполнятся при False!

    То что вы не проверяйте на равенство не устраняет проблему, другие сравнения так же подвержены ей. 

    Вычисления в отладчике и вычисления в программе не обязательно совпадают из за повышения типа, типа округления и т.п. 

    Другой интересный вопрос - что происходит при сравнении в конкретном языке? Целое преобразуется в float/double и потом сравнивается? Или же float/double преобразуется в целое и сравниваются целые значения? Честно говоря я не знаю как это делает VB. Надо смотреть IL который был сгенерирован. 

    Еще интереснее что результаты вычислений могут не совпадать при запуске под отладчиком и без, а так же типа сборки release/debug.

    Но почему бы вам не создать промежуточные значения для результата умножения и результата сравнения и не посмотреть чему они равны? Так же запустите под отладчиком и без, release и debug. Есть хороший шанс что вы сможете получить оба сообщения.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 мая 2021 г. 19:05
    Модератор
  • >Интересно что в начале 90х когда я был студентом (причем не по компьютерной специальности), об этих (и других - их не мало) особенностях рассказывали в курсе по вычислительной технике. Очевидно сейчас это тайна. :)

    Изучается и сейчас, даже в средней школе на информатике, насколько я помню. Проблема в том, что само знание представления с плавающей точкой еще не дает навыка избегать подобных багов, это приходит только с опытом реального применения их к расчетам, причем баги эти могут быть самыми разными. Я например правило "сравнивать float нужно с точностью до дельты" усвоил сразу, но все равно срезался на том, что пытался в цикле делать x+=0.01 и ожидал, что результат будет кратен 0.01. Я не думаю, что можно требовать знать это от всех пользователей, работающих с компьютером. Более реальное решение в том, чтобы эти проблемы до пользователей не доходили. Стандартные float и double быстрые, но неудобные для всего, кроме научных расчетов. Использование decimal, где это возможно, будет хорошим шагом вперед. В Excel, кстати, 1.6 * 10 равно 16 с точностью до 30 знаков после запятой, видимо, у него своя система вещественных чисел. 

    >Я проверяю не на равенство! А проблема в том, что проверяемое условие дает True в качестве результата, но при этом выполнятся блок, который должен выполнятся при False!

    Отладчик использует для вычисления выражений свой интерпретатор IL, который может иметь другие флаги параметров float по сравнению с живым процессом с CLR.

    Увы, эта проблема фундаментальна и пользователей от нее нельзя полностью изолировать. То что об этом рассказывают в школе есть хороший индикатор что это нужно знать. Но то что частенько появляются сообщения что "Эксель неверно считает" говорит о том что в одно ухо влетает, а из другого вылетает. :)

    Excel использует IEEE 754 как и все остальные:

    https://www.microsoft.com/en-us/microsoft-365/blog/2008/04/10/understanding-floating-point-precision-aka-why-does-excel-give-me-seemingly-wrong-answers/

    Скорее всего вы видите округление при выводе. 

    Decimal имеет весьма небольшой диапазон и тоже подвержен тому же эффекту. Просто проблема проявляется на другом наборе чисел и привыкшие к десятичной системе люди ее не замечают. :)


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 мая 2021 г. 19:18
    Модератор
  • я не пойму ты что попарится решил? Я задал конкретный вопрос и ожидал у слышать конкретный ответ, а не роман на тему философии программирования! Нет ответа проходи мимо!
    4 мая 2021 г. 19:28
  • я не пойму ты что попарится решил? Я задал конкретный вопрос и ожидал у слышать конкретный ответ, а не роман на тему философии программирования! Нет ответа проходи мимо!

    Вам попытались дать развернутый ответ, что вещественное число может быть как больше 1.6 так и меньше 1.6. Так как после определенного знака возникает просто мусор, но этот мусор может как прибавиться так и отняться от числа, предсказать результат очень сложно((( Поэтому и получился большой рассказ о проблеме с плавающей точкой. Отвечающие хотели по максимуму поделится своей мудростью, ну и слегка потролить типа воспоминаниями о своей молодости, гуру это простительно ;-) Не стоит принимать это на свой счет.

    4 мая 2021 г. 19:50
  • вот отличии от тебя я 5 лет учился по специальности программированию и я знаю, как преобразуются числа! мне твоя философия тут и даром не облокотилась!
    4 мая 2021 г. 20:03
  • вот отличии от тебя я 5 лет учился по специальности программированию и я знаю, как преобразуются числа! мне твоя философия тут и даром не облокотилась!

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


    This posting is provided "AS IS" with no warranties, and confers no rights.

    4 мая 2021 г. 20:14
    Модератор
  • ты банер - я еще не начинал хамить! нечего сказать по существу проходи мимо!
    • Изменено SergejS 4 мая 2021 г. 20:28
    4 мая 2021 г. 20:26
  • Сергей, будьте добры придерживаться формального общения на форумах, если Вам не нравится ответ, можете вполне спокойно не обращать на него внимание.

    Если Вам помог чей-либо ответ, пожалуйста, не забывайте жать на кнопку "Предложить как ответ" или "Проголосовать за полезное сообщение" Мнения, высказанные здесь, являются отражение моих личных взглядов, а не позиции корпорации Microsoft. Вся информация предоставляется "как есть" без каких-либо гарантий.



    5 мая 2021 г. 7:16
    Модератор
  • мой вопрос состоял в том, что почему ветвление в If происходит не по результату полученному в теле If, а как тут преобразуются числа совершенно неважно! А персонаж, начинает тут нести бред, не относящийся к теме заданного вопроса.
    5 мая 2021 г. 8:24
  • >Скорее всего вы видите округление при выводе. 

    Да, похоже, Excel даже если включить показ 30 знаков после запятой, все равно показывает округленное значение. Поэтому проблему можно воспроизвести только на более сложных случаях. 

    >Decimal имеет весьма небольшой диапазон и тоже подвержен тому же эффекту. Просто проблема проявляется на другом наборе чисел и привыкшие к десятичной системе люди ее не замечают. :)

    Это так, но Decimal позволяет избавиться от потери копеек при вычислениях денежных сумм в различных финансовых приложениях, а большой диапазон там часто не требуется. Поэтому в бизнес приложениях Decimal >> Double.

    >Другой интересный вопрос - что происходит при сравнении в конкретном языке? Целое преобразуется в float/double и потом сравнивается? Или же float/double преобразуется в целое и сравниваются целые значения? Честно говоря я не знаю как это делает VB. Надо смотреть IL который был сгенерирован. 

    Судя по IL, преобразуется во float и сравнивается:

      IL_001e:  ldloc.0
      IL_001f:  ldc.r4     10.
      IL_0024:  mul
      IL_0025:  ldloc.1
      IL_0026:  conv.r4
      IL_0027:  cgt

    где local 0 - float32 _maxP, local 1 - int32 _selectedP

    >мой вопрос состоял в том, что почему ветвление в If происходит не по результату полученному в теле If, а как тут преобразуются числа совершенно неважно! А персонаж, начинает тут нести бред, не относящийся к теме заданного вопроса.

    Рассуждения по поводу чисел с плавающей точкой здесь вполне относятся к теме вопроса, так как в рассматриваемой программе именно они и происходят. Это публичный форум, где каждый может высказаться, индивидуальной помощи никто не обещал. Почему результат If неверен я уже выдвинул одну гипотезу, что дело в разном поведении вычислителя выражений в отладчике и реальной программы. Подтвердить на практике не получилось. В один момент вроде удалось поймать, когда значения были разными, но после перезапуска студии они вновь оказались одинаковыми. Возможно, здесь задействован какой-то глобальный параметр, который влияет на точность вычислений, и зависит от многих факторов. Но есть одна тонкость, ведь 1.6*10 = 16, а это степень 2, соответственно оно должно быть точно представимо в IEEE Float! Почему точность здесь вообще влияет? Словом, проблема загадочная, и точного ответа пока дать нельзя...

    5 мая 2021 г. 16:54
  • написал тот же самый код на C# и там программа выполняется логически, то есть если в результат проверки - истина, то и выполняется блок для истины, а если ложь то и выполняется соответствующий блок кода, а на VB это не так, стоит над чем задуматься в Microsoft!
    6 мая 2021 г. 7:11
  • Ура, мне удалось поймать проблему. Проблема воспроизводится только на платформе x64, и в C# и в VB. Собственно вот, значения в отладчике и в реальной программе различаются:

    float debugger issue

    Как видно, это на 100% проблема точности вычислений с float. При x64 вычисления с float имеют такую же точность, как и double, и это сравнение может быть выполнено без потери точности. Но Visual Studio, в которой выполняется вычислитель выражений, всегда является 32-битным процессом. Поэтому в ней данная операция выполняется как есть, и приводит к потере точности. Проблема в таком виде проявляется в VS2019, а в VS2012 она проявляется наоборот, значения различаются на платформе x86, и в отладчике точность не теряется.  

    6 мая 2021 г. 8:46
  • я тестировал в VS2010 и в VS2019 там результаты с точностью до наоборот.
    • Изменено SergejS 6 мая 2021 г. 9:16
    6 мая 2021 г. 9:15