Ошибка при работе с числами с плавающей запятой

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

Ошибка при работе с числами с плавающей запятой

Сообщение VBTerminator » 18.07.2010 (Вс) 13:32

Допустим, у меня есть код, работающий с оными числами:
Код: Выделить всё
Dim a As Single

Private Sub Form_Load()
   
    Do While a < 1
        a = a + 0.01
        Debug.Print a
    Loop
   
End Sub

Ожидается, что он будет выводить числа от 0 до 1 с шагом 0.1.

Но результат превосходит все ожидания:
Код: Выделить всё
0,01
0,02
0,03
0,04
0,05
0,06
0,07
0,08
0,09
9,999999E-02
0,11
0,12
0,13
0,14
0,15
0,16
0,17
0,18
0,19
0,2
0,21
0,22
0,23
0,2400001
0,2500001
0,2600001
0,27
0,28
0,29
0,3
0,31
0,32
0,33
0,34
0,35
0,36
0,3699999
0,3799999
0,3899999
0,3999999
0,4099999
0,4199999
0,4299999
0,4399999
0,4499999
0,4599999
0,4699998
0,4799998
0,4899998
0,4999998
0,5099998
0,5199998
0,5299998
0,5399998
0,5499998
0,5599998
0,5699998
0,5799997
0,5899997
0,5999997
0,6099997
0,6199997
0,6299997
0,6399997
0,6499997
0,6599997
0,6699997
0,6799996
0,6899996
0,6999996
0,7099996
0,7199996
0,7299996
0,7399996
0,7499996
0,7599996
0,7699996
0,7799996
0,7899995
0,7999995
0,8099995
0,8199995
0,8299995
0,8399995
0,8499995
0,8599995
0,8699995
0,8799995
0,8899994
0,8999994
0,9099994
0,9199994
0,9299994
0,9399994
0,9499994
0,9599994
0,9699994
0,9799994
0,9899994
0,9999993
1,009999


Люди добрые! Скажите пожалуйста, что глючит: IDE, я или что-то другое? И как исправить эту проблему? А то я уже мозг поломал, не знаю, откуда вообще эти числа взялись! :(

Среда - Visual Basic 6.0 (SP 6)

iGrok
Артефакт VBStreets
Артефакт VBStreets
 
Сообщения: 4272
Зарегистрирован: 10.05.2007 (Чт) 16:11
Откуда: Сетевое сознание

Re: Ошибка при работе с числами с плавающей запятой

Сообщение iGrok » 18.07.2010 (Вс) 14:10

Глючишь ты. =)
IDE правильно работает. А всё дело в том, как хранятся числа с плавающей запятой.

Используй округление до нужного тебе кол-ва знаков после запятой.
label:
cli
jmp label

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

Re: Ошибка при работе с числами с плавающей запятой

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

Или прибавлять одну десятую, а не одну сотую. Ещё можно одной десятой явно указать тип @.
O, sancta simplicitas!

VBTerminator
Постоялец
Постоялец
Аватара пользователя
 
Сообщения: 415
Зарегистрирован: 19.11.2008 (Ср) 20:10

Re: Ошибка при работе с числами с плавающей запятой

Сообщение VBTerminator » 18.07.2010 (Вс) 14:45

Исправление работает, но сравнение дробных чисел всё равно вылетает в трубу.

Вышеприведённый код являлся упрощением следующего:
Код: Выделить всё
Public Const JUMP_SPEED      As Single = 0.01   'Скорость прыжка
Public Const JUMP_MAX_HEIGHT As Single = 1      'Максимальная высота прыжка

'Состояния прыжка
Public Const JUMP_NONE       As Byte = 0        'Стоим
Public Const JUMP_UP         As Byte = 1        'Летим вверх
Public Const JUMP_DOWN       As Byte = 2        'Падаем вниз


Public JumpHeight            As Single          'Текущая высота прыжка

Public Sub UpdateWorld()
   
    'Проверяем состояние прыжка
    Select Case JumpState
       
        'Если мы летим вверх, то ...
        Case JUMP_UP
            JumpHeight = Round(JumpHeight, 1) + JUMP_SPEED
           
            'Если у нас перелёт, опускаем персонажа вниз
            If Round(JumpHeight, 1) > JUMP_MAX_HEIGHT Then JumpState = JUMP_DOWN
           
        'Если мы летим вниз, ...
        Case JUMP_DOWN
            JumpHeight = JumpHeight - JUMP_SPEED
       
            'Если мы опустились, обнуляем высоту
            If Round(JumpHeight, 1) < 0 Then JumpHeight = 0: JumpState = JUMP_NONE
   
    End Select
   
End Sub

Но после присвоения JumpState = JUMP_UP персонаж почему-то улетает далеко-далеко и не думает опускаться на землю. Что ещё необходимо сделать?

iGrok
Артефакт VBStreets
Артефакт VBStreets
 
Сообщения: 4272
Зарегистрирован: 10.05.2007 (Чт) 16:11
Откуда: Сетевое сознание

Re: Ошибка при работе с числами с плавающей запятой

Сообщение iGrok » 18.07.2010 (Вс) 15:55

Я тебе уже сказал, "всё дело в том, как хранятся числа с плавающей запятой."
Эхх, жаль статью с главной vbstreets сейчас не найти. В общем, вот. Читай:
http://xpoint.ru/know-how/Articles/Floa ... s?comments

UPD: Перечитал твой код. Что-то не так. По идее, всё должно работать как надо.
Выводи отладочные сообщения со значениями JumpHeight, JUMP_MAX_HEIGHT. Округляй не до увеличения, а после.
label:
cli
jmp label

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Ошибка при работе с числами с плавающей запятой

Сообщение Хакер » 18.07.2010 (Вс) 16:05

iGrok писал(а):Эхх, жаль статью с главной vbstreets сейчас не найти.

http://vbstreets.ru/VB/Articles/66541.aspx
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: Ошибка при работе с числами с плавающей запятой

Сообщение arthur2 » 18.07.2010 (Вс) 17:49

Хакер писал(а):http://vbstreets.ru/VB/Articles/66541.aspx

Интересная пугалка в заключении статьи:
Формат чисел IEEE754 неплохо работает в технике, когда нужно представить и передать число, но требует абсолютного контроля в операциях деления и умножения.

Вот пример, который вы можете испытать:
Программный код в VB который делит два одинаковых числа 0,3 : 0,3 = 1 (это должно получится)

Private Sub Command1_Click()
Dim a, c As Single
a = 0.3
c = 0.3
Text1.Text = c / a
Text2.Text = a / c
End Sub

Вот результат вычислений этой программы:
деление одинаковых чисел в IEEE754

a=0.3 c=0.3 c : a = 1.00000003973643
a=0.3 c=0.3 a : c = 0.999999960263572
Здесь вы видите не только не точный результат, но и зависимость результата от расположения переменных. Это только одна операция, представьте, что операций тысячи. Результат таких вычислений будет не предсказуем.

Вот ещё пример: Text1.Text = (c / a - 1) * 10000000000
Если посчитать на бумажке ответ будет = 0, а если на компьютере = 397,364299242753
Как Вы считаете- есть ли разница между 0 и 397 (допустим в ядерных бомбах!)


Нужно заметить, что все эти ужасы происходят из-за этой вот строки: Dim a, c As SingleЗдесь 'а' по недоразумению объявлена как вариант. Если объявить обе переменные синглами, то никаких ужасов не произойдёт - первый пример вернёт предсказанную единицу, а второй - предсказанный ноль. Так что если какие ужасы в IEEE754 и есть, то этими "примерами" они не иллюстрируются.

Кстати, а почему variant так всё портит в этих примерах? Разве после присвоения а=0.3 переменная не становится подтипом single?
Артур
 
   

iGrok
Артефакт VBStreets
Артефакт VBStreets
 
Сообщения: 4272
Зарегистрирован: 10.05.2007 (Чт) 16:11
Откуда: Сетевое сознание

Re: Ошибка при работе с числами с плавающей запятой

Сообщение iGrok » 18.07.2010 (Вс) 18:14

Хакер, это разве та статья? Вроде было что-то от Вайпера, где он писал про то, как необходимо работать с числами с плав. точкой - с использованием некоторой "погрешности".

VBTerminator, в коде какая-то ерунда. Он в принципе не может работать.
Во-первых, округлять значения при сравнивании не надо, надо только перед выводом.
Во-вторых, ты округляешь до десятых, а прибавляешь сотые, причём после округления. После любого кол-ва итераций у тебя будет значение 0.01.

Если это исправить - всё работает. Да, наверное ещё неплохо бы использовать нестрогое сравнение.

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

Public Const JUMP_SPEED      As Single = 0.01   'Скорость прыжка
Public Const JUMP_MAX_HEIGHT As Single = 1      'Максимальная высота прыжка

'Состояния прыжка
Public Const JUMP_NONE       As Byte = 0        'Стоим
Public Const JUMP_UP         As Byte = 1        'Летим вверх
Public Const JUMP_DOWN       As Byte = 2        'Падаем вниз

Public JumpState As Byte

Public JumpHeight            As Single          'Текущая высота прыжка

Public Sub UpdateWorld()
   
    'Проверяем состояние прыжка
    Select Case JumpState
       
        'Если мы летим вверх, то ...
        Case JUMP_UP
            JumpHeight = JumpHeight + JUMP_SPEED
           
            'Если у нас перелёт, опускаем персонажа вниз
            If JumpHeight >= JUMP_MAX_HEIGHT Then JumpState = JUMP_DOWN
           
        'Если мы летим вниз, ...
        Case JUMP_DOWN
            JumpHeight = JumpHeight - JUMP_SPEED
       
            'Если мы опустились, обнуляем высоту
            If JumpHeight <= 0 Then JumpHeight = 0: JumpState = JUMP_NONE
   
    End Select
   
End Sub

Public Sub test()
  JumpState = JUMP_UP
Dim i As Long
  For i = 0 To 210
    Debug.Print "jh"; Round(JumpHeight, 2)
    UpdateWorld
  Next i
End Sub


arthur2 писал(а):Кстати, а почему variant так всё портит в этих примерах? Разве после присвоения а=0.3 переменная не становится подтипом single?

Всё ещё хуже. Портит, как ни странно, не Variant. Объяви a как Double, и посмотри что будет.
label:
cli
jmp label

arthur2
Продвинутый гуру
Продвинутый гуру
Аватара пользователя
 
Сообщения: 1688
Зарегистрирован: 23.01.2008 (Ср) 14:35

Re: Ошибка при работе с числами с плавающей запятой

Сообщение arthur2 » 18.07.2010 (Вс) 19:38

Если оба объявить как дабл, ничего не будет - единица и ноль. Если объявить одну даблом а другую синглом - вот тогда глюк и появляется, что и следовало ожидать :)

Я только не понял, вариант по умолчанию делает дробную переменнуд даблом?
Артур
 
   

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

Re: Ошибка при работе с числами с плавающей запятой

Сообщение alibek » 18.07.2010 (Вс) 19:51

arthur2 писал(а):Я только не понял, вариант по умолчанию делает дробную переменнуд даблом?

По умолчанию IDE считает целые числа Long, а дробные Double.
Поэтому a=0.3 равносильно a=0.3#.
Lasciate ogni speranza, voi ch'entrate.

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

Re: Ошибка при работе с числами с плавающей запятой

Сообщение alibek » 18.07.2010 (Вс) 19:55

По сабжу.
VBTerminator писал(а):Но результат превосходит все ожидания:


VBTerminator, тебя не удивляет, что (1/3+1/3+1/3)-1=0, а (0.3333333+0.3333333+0.3333333)-1=0.0000001?
Помедитируй над этим.
Lasciate ogni speranza, voi ch'entrate.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16478
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: Ошибка при работе с числами с плавающей запятой

Сообщение Хакер » 18.07.2010 (Вс) 19:55

alibek писал(а):IDE считает целые числа Long,

Неа, Integer-ом:
Код: Выделить всё
? typename(7);":"; typename(.7)
Integer:Double
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

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

Re: Ошибка при работе с числами с плавающей запятой

Сообщение alibek » 18.07.2010 (Вс) 19:56

А, верно, Integer.
Lasciate ogni speranza, voi ch'entrate.

iGrok
Артефакт VBStreets
Артефакт VBStreets
 
Сообщения: 4272
Зарегистрирован: 10.05.2007 (Чт) 16:11
Откуда: Сетевое сознание

Re: Ошибка при работе с числами с плавающей запятой

Сообщение iGrok » 18.07.2010 (Вс) 20:15

Вот так ещё забавнее:
Код: Выделить всё
?typename(1);":";typename(100000);":";typename(10000000000)
Integer:Long:Double


alibek писал(а):(0.3333333+0.3333333+0.3333333)-1=0.0000001

Только наоборот:
1-(0.3333333+0.3333333+0.3333333)=0.0000001
label:
cli
jmp label

VBTerminator
Постоялец
Постоялец
Аватара пользователя
 
Сообщения: 415
Зарегистрирован: 19.11.2008 (Ср) 20:10

Re: Ошибка при работе с числами с плавающей запятой

Сообщение VBTerminator » 18.07.2010 (Вс) 21:49

iGrok, большое спасибо за статью и работающий код! :)

alibek писал(а):VBTerminator, тебя не удивляет, что (1/3+1/3+1/3)-1=0, а (0.3333333+0.3333333+0.3333333)-1=0.0000001?
Помедитируй над этим.

Медитация вылилась в ступор. Высплюсь и попробую снова, может быть на меня снизойдёт просветление? :D

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

Re: Ошибка при работе с числами с плавающей запятой

Сообщение alibek » 18.07.2010 (Вс) 22:18

VBTerminator писал(а):Медитация вылилась в ступор. Высплюсь и попробую снова, может быть на меня снизойдёт просветление? :D

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

VBTerminator
Постоялец
Постоялец
Аватара пользователя
 
Сообщения: 415
Зарегистрирован: 19.11.2008 (Ср) 20:10

Re: Ошибка при работе с числами с плавающей запятой

Сообщение VBTerminator » 19.07.2010 (Пн) 9:56

alibek писал(а):По сабжу.
VBTerminator, тебя не удивляет, что (1/3+1/3+1/3)-1=0?

Это можно доказать и в десятичной системе счисления:
  1. 1/3 = 0,(3) ≈ 0,3333333333333334;
  2. 0,3333333333333334 * 3 = 1,000000002 (но из-за округления последняя двойка отбрасывается) ≈ 1;
  3. 1 - 1 = 0.

iGrok писал(а):1-(0.3333333+0.3333333+0.3333333)=0.0000001


  1. 0.3333333 * 3 = 0.9999999
  2. 1 - 0.9999999 = 0.0000001
ИМХО, здесь ошибки не происходит, т.к. в переменной ещё остаётся запас нулей справа (дробь конечная).

Я прав?
Последний раз редактировалось VBTerminator 19.07.2010 (Пн) 9:59, всего редактировалось 1 раз.

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

Re: Ошибка при работе с числами с плавающей запятой

Сообщение alibek » 19.07.2010 (Пн) 9:59

Нет, ты ничего не понял.
Медитируй еще.
Lasciate ogni speranza, voi ch'entrate.


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

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

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

    TopList