Невидимая ошибка.

Тема в разделе "WASM.BEGINNERS", создана пользователем Dukales, 18 сен 2009.

  1. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    Есть такой код:
    Код (Text):
    1. @@:
    2. FSTP b
    3. ...
    4. call f // double f(void)
    5. FLD qword ptr b
    6. FCOMIP ST, ST(1)
    7. ja @@
    Код зацикливается и к последней строчке стек FPU содержит следующие значения:

    Код (Text):
    1. ST(0) 4478,89237485945 // 400B 8BF7 2395 6E29 0000 - представление в виде DWORD'ов
    2. ST(1) 4478,89237485945 // 400B 8BF7 2395 6E28 FC58
    3. ST(2) Empty
    4. ST(3) Empty
    5. ST(4) Empty
    6. ST(5) Empty
    7. ST(6) Empty
    8. ST(7) Empty
    9.  
    10. CTRL 037F
    11. STAT 3020
    12. TAGS 0FFF
    13.  
    14. IM 1
    15. DM 1
    16. ZM 1
    17. OM 1
    18. UM 1
    19. PM 1
    20. PC 3
    21. RC 0
    22. IC 0
    23.  
    24. IE 0
    25. DE 0
    26. ZE 0
    27. OE 0
    28. UE 0
    29. PE 1
    30. SF 0
    31. ES 0
    32. C0 0
    33. C1 0
    34. C2 0
    35. ST 6
    36. C3 0
    37. BF 0
    Флаги CPU:
    Код (Text):
    1. EFLAGS: IF и AF == 1, остальные - 0
    Как я понимаю, различия между внутренним форматом представления FPU (tbyte) и форматом представления b (qword) как раз в последних двух байтах и видимо они относятся к мантиссе.
    Так вот хочу предостеречь от использования формата double в качестве типа для сохранения например значения целевой функции, а пользоваться long double, а то никогда такую ошибку не найдёте =)
    (в комментах плз замечания о существенности такого замечания =))
     
  2. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    *предпоследней :/
     
  3. valterg

    valterg Active Member

    Публикаций:
    0
    Регистрация:
    19 авг 2004
    Сообщения:
    2.105
    Разве это различия. FPU умеет считать и работать с точность 80 бит(полный формат), 64 и 32.
    Из твоего кода непонятно, где могло возникнуть различие. Во флагах я профан - может профи там увидят что-то.
     
  4. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    Код (Text):
    1. ST(0) 4478,89237485945 // 400B 8BF7 2395 6E29 0000
    2. ST(1) 4478,89237485945 // 400B 8BF7 2395 6E28 FC58
    - это то, что показывает окошко по Ctrl+U в Ctrl+Alt+C на Break point'е в Borland C++ Builder'е.
    Означает, что оба числа равны 4478,89237485945 в десятичном представлении с точностью формата double (можно ещё 2 знака записать - всего 17 получается значащих цифр, хотя скорее всего они тоже совпадут), а вот в шестнадцатеричном представлении, таком, которое и хранится в регистре FPU - после "//", они различаются.
    Из кода видно, что промежуточный результат, который должен уменьшаться, сохраняется в переменной b, которая имеет размер 8 байт - типа double (qword). Каждый раз в цикле функция f оставляет в регистре ST(0) число, которое всегда чуть больше, чем само же, но округлённое с long double до double. Поэтому зацикливается.
     
  5. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    кстати sprintf Borland'овская в любом случае округляет до double числа long double. Если сделать хоть
    Код (Text):
    1. sprintf(string, "%.30le", 0x7FEFFFFFFFFFFFFFl);
    хоть
    Код (Text):
    1. sprintf(string, "%.30Le", 0x7FFFFFFFFFFFFFFFFFFFL);
    то всё обрубится нулями на 19 знаке после запятой.
    А этот Borland C++ Builder похоже написан сам на себе, поэтому и не видно, что два числа 4478,89237485945 на самом деле не равны.
     
  6. max7C4

    max7C4 New Member

    Публикаций:
    0
    Регистрация:
    17 мар 2008
    Сообщения:
    1.203
    Dukales
    Почитай ка Юрова и разберись что где и как. Когда научишься писать толкай темы и не разводи треп.
    Со процессор работает только с 80-битным представлением вещественного числа, а то, что Вы тут толкаете, это элементарное накопление погрешности при циклических вычислениях.
    Короче учите матчасть.
     
  7. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    1. Не знаю кто такой Юров (негуглил).
    2. Разобрался без него уже давно. Повторяю - ошибка неочевидная - это очевидно из её описания.
    3. Умею писать. Грамотно толкнул тему (тема - не "вопрос", а "совет"). Трёп - это то, что пишут профаны, плохо читающие тему в ответах.
    4. Со процессор работает не только с 80 битным, но и с 32 и 64 битным представлениями вещественного чилса (в широком смысле). Есть как FLD tbyte ptr X так и FLD dword ptr X, FLD qword ptr X варианты инструкции FLD, например. Внутренне - да, не спорю.
    5. Где накопление погрешности? оО. Я не жаловался на него =). Я вам не сообщал об устройстве функции f - кто вам сказал, что погрешность какая-то есть? Такой сущности нет в программе, из которой я вам привёл участок кода.
    То малое уменьшение её значения за цикл (я подсчитал его - это где-то 4.15667500419659E-13) - является существенным и вообще к "погрешностям" не имеет отношения.
     
  8. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    max7C4
    Перечитайте внимательнее и уловите суть проблемы и не сорите терминами больше - очень доставляет.
    Если будет не понятно, то вот иллюстрация:

    Код (Text):
    1. #pragma pack(push, 1)
    2. typedef struct
    3. {
    4.  WORD w0, w1, w2, w3, w4;
    5. } t_tbyte;
    6. #pragma pack(pop)
    7.  
    8. t_tbyte a = {0x0000, 0x6E29, 0x2395, 0x8BF7, 0x400B}; // 4478.89237485945
    9. t_tbyte b = {0xFC58, 0x6E28, 0x2395, 0x8BF7, 0x400B}; // 4478.89237485945
    10.  
    11. t_tbyte c = {0x0000, 0x0000, 0x0000, 0xEA00, 0x3FD5}; // 4.15667500419659E-13 == (a - b) в FPU
    12.  
    13. #pragma warning (disable : 4035)
    14. void f(void)
    15. {
    16.  double temp; // 4478.89237485945 // 0x40B17EE472ADC520L
    17.  
    18.  asm
    19.  {
    20.   @@label:
    21.   FLD tbyte ptr a
    22.   FLD tbyte ptr b
    23.   FSTP temp
    24.   FLD temp
    25.   FCOMPP
    26.   FNSTSW AX
    27.   sahf
    28.   jne @@label
    29.  }
    30. }    
    31. #pragma warning (default : 4035)
    32.  
    33. #pragma startup f
    Код (на удивление, если не принимать в рассчёт сабж) не зацикливается, хотя и (b != a).
     
  9. Clear__Energy

    Clear__Energy New Member

    Публикаций:
    0
    Регистрация:
    30 янв 2009
    Сообщения:
    432
    ололо, двачеры :3
     
  10. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    Clear__Energy
    мой дядя из деревни так говорил
     
  11. valterg

    valterg Active Member

    Публикаций:
    0
    Регистрация:
    19 авг 2004
    Сообщения:
    2.105
    Вот этот код и надо было добавить в первом сообщении, если хотели советовать. Просто в теме Начинающие сразу руки чешутся подсказать, тем более что совет очень непонятно изложен и смахивает на вопрос. Про осторожность при сравнении знали все хорошие программисты на Фортране и Си еще в прошлом веке. А уж если человек программирует на ассемблере FPU он ваш совет знает :)
     
  12. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    спасибо. буду корректнее =)
     
  13. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Dukales
    Помимо погрешности вычислений еще есть погрешность представления вещественного числа, т.к. далеко не каждое дробное число можно точно разложить по степеням 2. Например, даже такое "круглое" в десятичном виде число как 1.2 не может быть представлено точно в двоичном виде, поэтому при загрузке в FPU чисел (float)1.2, (double)1.2 и (long double)1.2 - они не будут равны друг другу, т.к. каждое из них будет содержать погрешность округления в младшем разряде своей мантиссы.
     
  14. max7C4

    max7C4 New Member

    Публикаций:
    0
    Регистрация:
    17 мар 2008
    Сообщения:
    1.203
    leo
    +1 вот про это-то я и говорил, но больше говорить не буду.
     
  15. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    leo
    не погрешность, а дискретность.

    5. Где накопление погрешности?
    Помимо погрешности вычислений еще есть погрешность представления вещественного числа

    она не накапливается.
     
  16. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Не дискретность, а ограниченная разрядность -> ограниченная точность -> погрешность представления в заданном формате

    Во-первых, однажды возникнув она может изменяться = "накапливаться" в результате серии операций. Или ты считаешь, что у числа (1.2*3) будет та же самая погрешность представления, что и у исходного 1.2 или результирующего 3.6 ?
    Во-вторых, когда пытаешься сравнивать числа на точное равентство\неравенство, то нет разницы - образовалась ли погрешность за счет одной операции или накопилась в результате вычислений, т.к. в любом случае можешь получить обломс ;)

    Поэтому чтобы не нарываться на "невидимые ошибки" и не "предостерегать от использования формата double" нужно всегда сравнивать вещественные числа c учетом возможной погрешости в младшем разряде мантиссы ;)
     
  17. Dukales

    Dukales New Member

    Публикаций:
    0
    Регистрация:
    5 июл 2009
    Сообщения:
    199
    мне, как человеку имеющему во, слово "погрешность" режет слух, а вам?
    в моём случае она постоянная.
    Вся проблема в том, что если функция объявлена как
    Код (Text):
    1. _stdcall double f(...);
    , то я и сравниваю с double и считаю, что всё учёл. И вроде-бы прав, но даже функция, тело которой я не вижу, а она на деле объявлена как
    Код (Text):
    1. _stdcall double mul(double a, double b)
    2. {
    3.  asm
    4.  {
    5.   FLD a
    6.   FLD b
    7.   FMUL
    8.  }
    9. }
    при использовании приведёт когда-нибудь к подобной же ошибке. Для _stdcall и других, в общем, результат - число с плавающей запятой - принято возвращать в ST(0). То есть все функции
    Код (Text):
    1. _stdcall float mul(double a, double b);
    2. _stdcall double mul(double a, double b);
    3. _stdcall long double mul(double a, double b);
    имеют одинаковый код (абсолютно), хотя лучше бы потребовать в соглашении о вызове перед ret делать для float и double что-то типа:
    Код (Text):
    1. // float:
    2. ...
    3. add ESP, -4
    4. FSTP dword ptr [ESP]
    5. FLD dword ptr [ESP]
    6. add ESP, 4
    7. ret
    8. ...
    9. // double:
    10. ...
    11. add ESP, -8
    12. FSTP qword ptr [ESP]
    13. FLD qword ptr [ESP]
    14. add ESP, 8
    15. ret
    16. ...