Сравнение Single с Double

Программирование на Visual Basic, главный форум. Обсуждение тем программирования на VB 1—6.
Даже если вы плохо разбираетесь в VB и программировании вообще — тут вам помогут. В разумных пределах, конечно.
Правила форума
Темы, в которых будет сначала написано «что нужно сделать», а затем просьба «помогите», будут закрыты.
Читайте требования к создаваемым темам.
Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Сравнение Single с Double

Сообщение Mikle » 17.01.2018 (Ср) 19:59

Код:
Код: Выделить всё
  Dim s As Single
  Dim d As Double

  s = 1 / 6
  d = 1 / 6
  Debug.Print s, d, s = d

Результат:
Код: Выделить всё
0,1666667     0,166666666666667          True


Пытаюсь объяснить - при сравнении Single с Double бейсик сначала приводит Double к Single, тогда результат объясним.
Но как тогда получается такое:
Код: Выделить всё
  Dim s As Single
  Dim d As Double

  s = 1 / 6
  d = 0.1666667
  Debug.Print s, d, s = d

Код: Выделить всё
0,1666667    0,1666667    False


Но и такое:
Код: Выделить всё
  Dim d As Double

  s = 0.1666667
  d = 0.1666667
  Debug.Print s, d, s = d

Код: Выделить всё
0,1666667    0,1666667    True

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 17.01.2018 (Ср) 22:41

Даже так:
Код: Выделить всё
  Dim s1 As Single
  Dim s2 As Single
  Dim d1 As Single
  Dim d2 As Single

  s1 = 1 / 6
  s2 = 0.1666667
  d1 = s1
  d2 = s2
  Debug.Print d1, d2, d1 = d2

Код: Выделить всё
0,1666667     0,1666667    False

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 18.01.2018 (Чт) 1:16

Нужно просмотреть байты (присвоив значение байтовому массиву через LSet или CopyMemory), тогда вопроса не будет, т.к. значения разные.
Отображаемое десятичное значение не имеет ничего общего с тем, как это значение хранится в двоичном виде с ограниченной точностью. В двоичном виде с плавающей запятой 0,1666667 отличается от 1/6.
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 18.01.2018 (Чт) 9:29

alibek писал(а):Отображаемое десятичное значение не имеет ничего общего с тем, как это значение хранится в двоичном виде с ограниченной точностью. В двоичном виде с плавающей запятой 0,1666667 отличается от 1/6

Естественно, я понимаю, что никакая натуральная дробь, в знаменателе которой не степень двойки, не может быть представлена конечной двоичной дробью. При этом ещё десятичная запись так же накладывает свои ограничения.
Отсюда я сделал естественный вывод, что в Single и Double такие дроби представлены с разной точностью, а значит не будут равны. Проверил - они оказались неожиданно равны, при этом по десятичной записи неравны.
Следующий вывод - видимо, бейсик перед сравнением приводит типы к Single.
Но такой эксперимент дал неравенство, при равенстве десятичных записей:
Код: Выделить всё
  Dim s1 As Single
  Dim s2 As Single

  s1 = 1 / 6
  s2 = 0.1666667
  Debug.Print s1, s2, s1 = s2

Тут уже нет приведения типов, только Single.
Делаю следующий вывод - очевидно, десятичная запись сделана с чрезмерным ограничением кол-ва значащих цифр, предположительно для того, чтобы дроби типа 1/5 выглядели "красиво", 0.2, а не как-то так: 0.20000001.
Тут уже возникла мысль проверить биты, но сначала я сделал более простую проверку, приравнял эти синглы к даблам, точность их десятичной записи многократно выше, поэтому я должен увидеть разницу и без проверки бит. Однако даблы так же оказались неравны при сравнении, а их десятичная запись совпала с записью синглов.
Тут уже я теряюсь, что предположить, разве что оптимизации компилятора, но это даёт тот же результат в отладчике, даже с точками останова на каждой строке, где я просматриваю значения переменных, то есть оптимизации отпадают.
Доберусь до бейсика, проверю биты.

В последней проверке банальная опечатка, я задавал даблы синглами: Dim d1 As Single

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 18.01.2018 (Чт) 21:07

Для точности эксперимента выражение нужно записывать таким образом, чтобы избегать неявных преобразований.
Вместо 1/6 нужно указать 1#/6# (для чисел двойной точности) и 1!/6! (для чисел одинарной точности, но помоему это по умолчанию). А вместо 0.1666667 записывать 0.1666667# и 0.1666667! соответственно.
Или в крайнем случае перед выражением указать ноль с нужной точностью (0!+1/6, 0!+0.1666667), но не уверен, что это будет корректно для теста.
Lasciate ogni speranza, voi ch'entrate.

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 18.01.2018 (Чт) 21:10

Mikle писал(а):Делаю следующий вывод - очевидно, десятичная запись сделана с чрезмерным ограничением кол-ва значащих цифр, предположительно для того, чтобы дроби типа 1/5 выглядели "красиво", 0.2, а не как-то так: 0.20000001.

Вообще-то в IEEE 754 довольно много всяких неожиданных нюансов — например положительные и отрицательные нули, или разная точность в разных диапазонах.
И если иногда отображение значений выглядит неожиданно, это из-за таких нюансов «под капотом».
Lasciate ogni speranza, voi ch'entrate.

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 18.01.2018 (Чт) 23:23

Насколько корректно сравнивать переменные разных типов? Мне кажется перед сравнение надо привести их к одному типу

Код: Выделить всё
  Dim s As Single
  Dim d As Double

  s = 1 / 6
  d = 1 / 6
 
  Debug.Print s, d, CDbl(s) = CDbl(d)


Результат:

Код: Выделить всё
0.1666667     0.166666666666667          False

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 20.01.2018 (Сб) 12:06

jangle писал(а):Насколько корректно сравнивать переменные разных типов? Мне кажется перед сравнение надо привести их к одному типу

Мы можем сравнивать Long и Byte, и никто не сомневается в корректности. В чём тут разница? Числа либо равны, либо нет. Тип Single без потерь приводится к Double, но vb почему-то по умолчанию делает приведение к Single, считаю это неправильным.
Второе неправильное - ограничение кол-ва значащих цифр в десятичной записи, можно наткнуться на разные курьёзы.

Мозговыносящий пример:
Смотря на код и результат, попытайтесь объяснить.
Вложения
SD.zip
(1.17 Кб) Скачиваний: 182

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 20.01.2018 (Сб) 15:18

Ничего мозговыносящего, просто разные значения сравниваются

Код: Выделить всё
  Private Sub Form_Load()
  Dim s1 As Single
  Dim s2 As Single
  Dim d As Double

  AutoRedraw = True

  s1 = 1!
  s2 = 1.0000005!
  Print s1, s2, s1 = s2
End Sub

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 20.01.2018 (Сб) 17:03

Насчет темы топика, сама постановка вопроса о сравнении вещественных чисел через "=" неправильна, и не может приводить к корректному результату.
В общем решение описано тут: https://www.working-software.com/cpp-float-comparisons

Предлагается сравнивать числа с плавающей запятой преобразованием к целочисленной переменной.

Код: Выделить всё
bool AlmostEqual2sComplement(float A, float B, int maxUlps)
{
    // Make sure maxUlps is non-negative and small enough that the
    // default NAN won't compare as equal to anything.
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);
    int aInt = *(int*)&A;
    // Make aInt lexicographically ordered as a twos-complement int
    if (aInt < 0)
        aInt = 0x80000000 - aInt;
    // Make bInt lexicographically ordered as a twos-complement int
    int bInt = *(int*)&B;
    if (bInt < 0)
        bInt = 0x80000000 - bInt;
    int intDiff = abs(aInt - bInt);
    if (intDiff <= maxUlps)
        return true;
   return false;
}

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 20.01.2018 (Сб) 17:07

Еще полезная инфа тут: https://randomascii.wordpress.com/2012/ ... 2-edition/

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 20.01.2018 (Сб) 21:00

Mikle писал(а):Мы можем сравнивать Long и Byte, и никто не сомневается в корректности. В чём тут разница?

Разница тут принципиальная.
Целые числа бесконечны. Но в пределах области их применения их количество конечно и для конкретной задачи всегда можно подобрать достаточную разрядность для их хранения. Например если в области применения возможны значения только в диапазоне от 0 до 10000, то для хранения этих значений достаточно 14 бит. И поэтому в сравнении целочисленных типов нет никаких сложностей, если значения не выходят за рамки разрядности.
Вещественные числа бесконечны не только на всем пространстве возможных значений, но и в любом ограниченном диапазоне. То есть для вещественных чисел в принципе нельзя подобрать такое число бит, которого хватит для хранения любых возможных значений в области применения, поскольку это будет зависеть от требуемой точности.
Если требуемая точность конечна, то это ничем принципиально не отличается от целочисленных типов (то есть типы fixed, decimal).
Если же требуется хранить вещественные числа с плавающей запятой, то это совсем другое дело и тут математика уже нетривиальна. И в частности для float в принципе неверно применять проверку на равенство, даже если значения получаются из чисел, кратных степеням двойки.
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 20.01.2018 (Сб) 21:31

alibek, jangle, печально,что кто-то из вас даже не скачал пример перед ответом.

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 20.01.2018 (Сб) 22:38

Mikle писал(а):alibek, jangle, печально,что кто-то из вас даже не скачал пример перед ответом.


screen.png
screen.png (34.39 Кб) Просмотров: 6290

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 21.01.2018 (Вс) 4:18

Я надеялся на просмотр в среде vb6.

pronto
Постоялец
Постоялец
 
Сообщения: 597
Зарегистрирован: 04.12.2005 (Вс) 6:20
Откуда: Владивосток

Re: Сравнение Single с Double

Сообщение pronto » 21.01.2018 (Вс) 7:14

Действительно, вынос мозга :bom:
s1 - s2 = -4,768372E-07 при том, что значения одинаковые и типы тоже...
Что самое весёлое — эффект плавающий. Достаточно один раз запустить отладчик без суффикса ! после числа, то последующие подстановки не влияют на результат.
добавлено позже: в исходном коде s2 = 1.0000005! в отладчике отображается как s2 = 1!.
O, sancta simplicitas!

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 3013
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Нидерланды

Re: Сравнение Single с Double

Сообщение jangle » 21.01.2018 (Вс) 14:52

Mikle писал(а):Я надеялся на просмотр в среде vb6.


К сожалению сейчас нет установленного VB под рукой. Там что какой-то Unicode символ вставлен?

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 21.01.2018 (Вс) 16:26

jangle писал(а):Там что какой-то Unicode символ вставлен?

Ничего подобного. В том то и дело, в исходнике (если просмотреть блокнотом) так:
Код: Выделить всё
  s1 = 1!
  s2 = 1.0000005!

А VB6 показывает так:
Код: Выделить всё
  s1 = 1!
  s2 = 1!

Но Print s1, s2, s1 = s2 при этом даёт такой результат:
Код: Выделить всё
1    1    False

Не посмотрев в блокноте, можно и правда голову сломать.

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 21.01.2018 (Вс) 16:50

Код: Выделить всё
Option Explicit

Type SingleData
  data As Single
End Type
Type ByteArray
  data(0 To 3) As Byte
End Type

Sub test()
Dim s1 As SingleData, s2 As SingleData
Dim b1 As ByteArray, b2 As ByteArray, i As Long
s1.data = 1! 'это 1
s2.data = 1! 'это 1.0000005
LSet b1 = s1
LSet b2 = s2
Debug.Print "values", s1.data, s2.data, s1.data = s2.data
For i = 0 To 3
Debug.Print "bytes", i, b1.data(i), b2.data(i)
Next i
End Sub

Код: Выделить всё
values         1             1            False
bytes          0             0             4
bytes          1             0             0
bytes          2             128           128
bytes          3             63            63

Мне кажется, что никаких вопросов тут возникать не должно.
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 21.01.2018 (Вс) 17:10

alibek писал(а):Мне кажется, что никаких вопросов тут возникать не должно.

А вопросов не возникает, я просто ДЕМОНСТРИРУЮ некоторые не совсем очевидные вещи, на которые можно натолкнуться и удивляться.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 21.01.2018 (Вс) 19:13

Решил проверить в vb.net, там при вводе константы 1.0000005! среда исправляет её до 1.00000048!
Но вот функция Str и метод ToString по-прежнему выдают строку "1".
Сравнивает Single и Double vb.net уже корректно, не приводя к Single.

Vi
Постоялец
Постоялец
 
Сообщения: 739
Зарегистрирован: 25.01.2002 (Пт) 11:03
Откуда: Россия, Ижевск

Re: Сравнение Single с Double

Сообщение Vi » 24.01.2018 (Ср) 16:41

alibek писал(а):А вопросов не возникает, я просто ДЕМОНСТРИРУЮ некоторые не совсем очевидные вещи, на которые можно натолкнуться и удивляться.

s = 1 / 6
Это число вычисляется через матпроцессор и равно в нём 0,166...666 с большой точностью (64 или 96 бит вместо 16 вSingle или 32 в Double)
d = 0.1666667
Это число компилируется и хранится с заданной точностью и, скорее равно, при загрузке в матпроцессор 0.1666667000...000
При сравнении матпроцессором в режиме ни float=Single ни double=Double эти числа не равны.

Когда мы выводим эти числа, то выводящая система округляет до заданного количества цифр после запятой, и получаются одинаковые строки.

VB6 тоже при автоматическом выравнивании вычисляет константы и выводит их в строку, но почему-то он в результате генерит s2 == s1 + delta, хотя рисует s2 == s1. Это я не понимаю.

Я сохранил твой пример после вставки исходного текста: никакого s2 = 1.0000005! в файле frm нет, только s2 = 1!, как VB6 и показывает в тексте окна.

PS
Я всегда убирал такие модификации, чтобы можно было легко переносить тексты, например, вот так:
s1 = CSng(1)
s2 = CSng(1.0000005)
Vita
Выше головы не прыгнешь, ниже земли не упадешь, дальше границы не убежишь! (с) КВН

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 24.01.2018 (Ср) 19:18

Пример можно воспроизвести проще - написать s = 1.000005, перейти курсором на следующую строку. Затем вставить 0 после десятичной точки и вновь перейти курсором на другую строку - в окне редактора число замениться на 1, но фактически будет равно 1.0000005, пока в этой строке не будет произведено редактирование.

Наиболее наглядную особенность fliat-чисел я увидел в 3D Studio, когда делал масштабную модель солнечной системы и внезапно обнаружил, что Плутон превратился из сферы в непонятный комок.
Lasciate ogni speranza, voi ch'entrate.

Mikle
Изобретатель велосипедов
Изобретатель велосипедов
Аватара пользователя
 
Сообщения: 4148
Зарегистрирован: 25.03.2003 (Вт) 14:02
Откуда: Туапсе

Re: Сравнение Single с Double

Сообщение Mikle » 24.01.2018 (Ср) 19:22

alibek писал(а):Пример можно воспроизвести проще - написать s = 1.000005, перейти курсором на следующую строку. Затем вставить 0 после десятичной точки и вновь перейти курсором на другую строку - в окне редактора число замениться на 1, но фактически будет равно 1.0000005, пока в этой строке не будет произведено редактирование.

Да, я примерно так и делал, когда экспериментировал, в том и опасность, что это можно получить даже случайно, не редактируя исходник в блокноте.
Только нужно ставить суффикс "!".

alibek
Большой Человек
Большой Человек
 
Сообщения: 14205
Зарегистрирован: 19.04.2002 (Пт) 11:40
Откуда: Russia

Re: Сравнение Single с Double

Сообщение alibek » 24.01.2018 (Ср) 19:46

Mikle писал(а):Только нужно ставить суффикс "!".

Это в любом случае не лишнее.
Я по возможности всегда указываю суффиксы типов, соответствующие переменным.
Lasciate ogni speranza, voi ch'entrate.


Вернуться в Visual Basic 1–6

Кто сейчас на конференции

Сейчас этот форум просматривают: SemrushBot и гости: 28

    TopList