Есть такой код: Код (Text): @@: FSTP b ... call f // double f(void) FLD qword ptr b FCOMIP ST, ST(1) ja @@ Код зацикливается и к последней строчке стек FPU содержит следующие значения: Код (Text): ST(0) 4478,89237485945 // 400B 8BF7 2395 6E29 0000 - представление в виде DWORD'ов ST(1) 4478,89237485945 // 400B 8BF7 2395 6E28 FC58 ST(2) Empty ST(3) Empty ST(4) Empty ST(5) Empty ST(6) Empty ST(7) Empty CTRL 037F STAT 3020 TAGS 0FFF IM 1 DM 1 ZM 1 OM 1 UM 1 PM 1 PC 3 RC 0 IC 0 IE 0 DE 0 ZE 0 OE 0 UE 0 PE 1 SF 0 ES 0 C0 0 C1 0 C2 0 ST 6 C3 0 BF 0 Флаги CPU: Код (Text): EFLAGS: IF и AF == 1, остальные - 0 Как я понимаю, различия между внутренним форматом представления FPU (tbyte) и форматом представления b (qword) как раз в последних двух байтах и видимо они относятся к мантиссе. Так вот хочу предостеречь от использования формата double в качестве типа для сохранения например значения целевой функции, а пользоваться long double, а то никогда такую ошибку не найдёте =) (в комментах плз замечания о существенности такого замечания =))
Разве это различия. FPU умеет считать и работать с точность 80 бит(полный формат), 64 и 32. Из твоего кода непонятно, где могло возникнуть различие. Во флагах я профан - может профи там увидят что-то.
Код (Text): ST(0) 4478,89237485945 // 400B 8BF7 2395 6E29 0000 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. Поэтому зацикливается.
кстати sprintf Borland'овская в любом случае округляет до double числа long double. Если сделать хоть Код (Text): sprintf(string, "%.30le", 0x7FEFFFFFFFFFFFFFl); хоть Код (Text): sprintf(string, "%.30Le", 0x7FFFFFFFFFFFFFFFFFFFL); то всё обрубится нулями на 19 знаке после запятой. А этот Borland C++ Builder похоже написан сам на себе, поэтому и не видно, что два числа 4478,89237485945 на самом деле не равны.
Dukales Почитай ка Юрова и разберись что где и как. Когда научишься писать толкай темы и не разводи треп. Со процессор работает только с 80-битным представлением вещественного числа, а то, что Вы тут толкаете, это элементарное накопление погрешности при циклических вычислениях. Короче учите матчасть.
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) - является существенным и вообще к "погрешностям" не имеет отношения.
max7C4 Перечитайте внимательнее и уловите суть проблемы и не сорите терминами больше - очень доставляет. Если будет не понятно, то вот иллюстрация: Код (Text): #pragma pack(push, 1) typedef struct { WORD w0, w1, w2, w3, w4; } t_tbyte; #pragma pack(pop) t_tbyte a = {0x0000, 0x6E29, 0x2395, 0x8BF7, 0x400B}; // 4478.89237485945 t_tbyte b = {0xFC58, 0x6E28, 0x2395, 0x8BF7, 0x400B}; // 4478.89237485945 t_tbyte c = {0x0000, 0x0000, 0x0000, 0xEA00, 0x3FD5}; // 4.15667500419659E-13 == (a - b) в FPU #pragma warning (disable : 4035) void f(void) { double temp; // 4478.89237485945 // 0x40B17EE472ADC520L asm { @@label: FLD tbyte ptr a FLD tbyte ptr b FSTP temp FLD temp FCOMPP FNSTSW AX sahf jne @@label } } #pragma warning (default : 4035) #pragma startup f Код (на удивление, если не принимать в рассчёт сабж) не зацикливается, хотя и (b != a).
Вот этот код и надо было добавить в первом сообщении, если хотели советовать. Просто в теме Начинающие сразу руки чешутся подсказать, тем более что совет очень непонятно изложен и смахивает на вопрос. Про осторожность при сравнении знали все хорошие программисты на Фортране и Си еще в прошлом веке. А уж если человек программирует на ассемблере FPU он ваш совет знает
Dukales Помимо погрешности вычислений еще есть погрешность представления вещественного числа, т.к. далеко не каждое дробное число можно точно разложить по степеням 2. Например, даже такое "круглое" в десятичном виде число как 1.2 не может быть представлено точно в двоичном виде, поэтому при загрузке в FPU чисел (float)1.2, (double)1.2 и (long double)1.2 - они не будут равны друг другу, т.к. каждое из них будет содержать погрешность округления в младшем разряде своей мантиссы.
leo не погрешность, а дискретность. 5. Где накопление погрешности? Помимо погрешности вычислений еще есть погрешность представления вещественного числа она не накапливается.
Не дискретность, а ограниченная разрядность -> ограниченная точность -> погрешность представления в заданном формате Во-первых, однажды возникнув она может изменяться = "накапливаться" в результате серии операций. Или ты считаешь, что у числа (1.2*3) будет та же самая погрешность представления, что и у исходного 1.2 или результирующего 3.6 ? Во-вторых, когда пытаешься сравнивать числа на точное равентство\неравенство, то нет разницы - образовалась ли погрешность за счет одной операции или накопилась в результате вычислений, т.к. в любом случае можешь получить обломс Поэтому чтобы не нарываться на "невидимые ошибки" и не "предостерегать от использования формата double" нужно всегда сравнивать вещественные числа c учетом возможной погрешости в младшем разряде мантиссы
мне, как человеку имеющему во, слово "погрешность" режет слух, а вам? в моём случае она постоянная. Вся проблема в том, что если функция объявлена как Код (Text): _stdcall double f(...); , то я и сравниваю с double и считаю, что всё учёл. И вроде-бы прав, но даже функция, тело которой я не вижу, а она на деле объявлена как Код (Text): _stdcall double mul(double a, double b) { asm { FLD a FLD b FMUL } } при использовании приведёт когда-нибудь к подобной же ошибке. Для _stdcall и других, в общем, результат - число с плавающей запятой - принято возвращать в ST(0). То есть все функции Код (Text): _stdcall float mul(double a, double b); _stdcall double mul(double a, double b); _stdcall long double mul(double a, double b); имеют одинаковый код (абсолютно), хотя лучше бы потребовать в соглашении о вызове перед ret делать для float и double что-то типа: Код (Text): // float: ... add ESP, -4 FSTP dword ptr [ESP] FLD dword ptr [ESP] add ESP, 4 ret ... // double: ... add ESP, -8 FSTP qword ptr [ESP] FLD qword ptr [ESP] add ESP, 8 ret ...