Недопустимая операция вызова Trunc с плавающей запятой()
я получаю (повторяемое) исключение с плавающей запятой, когда я пытаюсь Trunc()
a Real
значение.
например:
Trunc(1470724508.0318);
на самом деле фактический код сложнее:
ns: Real;
v: Int64;
ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;
v := Trunc(ns);
но в конце концов это все равно сводится к:
Trunc(ARealValue);
теперь я не могу повторить это нигде - только в этом месте. Где он терпит неудачу каждый раз.
это не вуду
к счастью, компьютеры не магия. процессор Intel выполняет очень специфические наблюдаемые действия. Поэтому я должен быть в состоянии понять, почему операция с плавающей запятой терпит неудачу.
вход в окно CPU
v: = Trunc (ns)
fld qword ptr [ebp-]
это загружает 8-байтовое значение с плавающей запятой в ebp - $ 10 в регистр с плавающей запятой ST0
.
байты по адресу памяти [ebp - $10]:
0018E9D0: 6702098C 41D5EA5E (as DWords)
0018E9D0: 41D5EA5E6702098C (as QWords)
0018E9D0: 1470724508.0318 (as Doubles)
вызов выполняется успешно, и регистр с плавающей запятой содержит соответствующее значение:
Далее идет фактический вызов функции RTL Trunc:
call @TRUNC
Далее идет кишки функции Trunc Delphi RTL:
@TRUNC:
sub esp,c wait fstcw word ptr [esp] //Store Floating-Point Control Word on the stack wait fldcw word ptr [cwChop] //Load Floating-Point Control Word fistp qword ptr [esp+] //Converts value in ST0 to signed integer //stores the result in the destination operand //and pops the stack (increments the stack pointer) wait fldcw word ptr [esp] //Load Floating-Point Control Word pop ecx pop eax pop edx ret
или я полагаю, что я мог бы просто вставить его из rtl, а не транскрибировать его из окна CPU:
const cwChop : Word = F32;
procedure _TRUNC;
asm
{ -> FST(0) Extended argument }
{ <- EDX:EAX Result }
SUB ESP,12
FSTCW [ESP] //Store foating-control word in ESP
FWAIT
FLDCW cwChop //Load new control word F32
FISTP qword ptr [ESP+4] //Convert ST0 to int, store in ESP+4, and pop the stack
FWAIT
FLDCW [ESP] //restore the FPCW
POP ECX
POP EAX
POP EDX
end;
исключение происходит во время фактический fistp операции.
fistp qword ptr [esp+]
в момент этого звонка ST0 register будет содержать то же значение с плавающей запятой:
Примечание: внимательный наблюдатель заметит, что значение на приведенном выше скриншоте не соответствует первому скриншоту. Это потому, что я взял его на другой бегать. Я бы предпочел не тщательно переделывать все константы в вопросе, чтобы просто сделать они последовательны - но поверьте мне: это то же самое, когда я достигаю
fistp
инструкция как это было послеfld
инструкция.
до этого:
-
sub esp,c
: я смотрю, как он толкает стек вниз на 12 байт -
fstcw word ptr [esp]
: я смотрю, как он нажимает $027F в текущий указатель стека -
fldcw word ptr [cwChop]
: я наблюдаю за изменением флагов управления с плавающей запятой -
fistp qword ptr [esp+]
: и он собирается написать Int64 в комнату он сделал на стеке
и затем он падает.
что на самом деле здесь происходит?
это происходит и с другими значениями, это не похоже на то, что что-то не так с этим конкретным значением с плавающей запятой. Но я даже попытался настроить тест в другом месте.
зная, что 8-байтовое шестнадцатеричное значение float:D5EA5E6702098C
, я попытался придумать настройку:
var
ns: Real;
nsOverlay: Int64 absolute ns;
v: Int64;
begin
nsOverlay := d62866a2f270dc;
v := Trunc(ns);
end;
что дает:
nsOverlay: = $ 41d62866a2f270dc;
mov [ebp-],$a2f270dc mov [ebp-],d62866
v: = Trunc (ns)
fld qword ptr [ebp-] call @TRUNC
и по call
до @trunc
, регистр с плавающей запятой ST0 содержит значение:
но вызов не провал. Он не только каждый раз в этом разделе моего кода.
что может быть возможно, это приводит к тому, что CPU бросает invalid floating point exception
?
каково значение cwChop
перед загрузкой управляющего слова?
значение cwChop
выглядит правильным перед загрузить управляющее слово, F32
. Но после нагрузки фактический контрольное слово неверно:
Бонус Треп
фактическая функция, которая терпит неудачу, - это что-то конвертировать высокопроизводительные отсчеты тика в наносекунды:
function PerformanceTicksToNs(const HighPerformanceTickCount: Int64): Int64;
//Convert high-performance ticks into nanoseconds
var
ns: Real;
v: Int64;
begin
Result := 0;
if HighPerformanceTickCount = 0 then
Exit;
if g_HighResolutionTimerFrequency = 0 then
Exit;
ns := ((HighPerformanceTickCount*1.0)/g_HighResolutionTimerFrequency) * 1000000000;
v := Trunc(ns);
Result := v;
end;
я создал все временные переменные intermeidate, чтобы попытаться отследить, где происходит сбой.
я даже пытался использовать это в качестве шаблона, чтобы попытаться воспроизвести его:
var
i1, i2: Int64;
ns: Real;
v: Int64;
vOver: Int64 absolute ns;
begin
i1 := 5060170;
i2 := 3429541;
ns := ((i1*1.0)/i2) * 1000000000;
//vOver := d62866a2f270dc;
v := Trunc(ns);
но он отлично работает. Есть что-то о том, когда он вызывается во время модульного теста DUnit.
флаги контрольного слова с плавающей запятой
стандартное управляющее слово Delphi: 32
:
32 = 0001 00 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;reserved
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
00 ;Rounding control -
0 ;Infinity control - 0 (not used)
требуемое значение Windows API: 7F
7F = 0000 00 10 01 111111
1 ;Allow invalid numbers
1 ;Allow denormals (very small numbers)
1 ;Allow divide by zero
1 ;Allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;reserved
10 ;Precision Control - 10B (double precision)
00 ;Rounding control
0 ;Infinity control - 0 (not used)
на crChop
управляющее слово: F32
F32 = 0001 11 11 00 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
0 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding Control
1 ;Infinity control - 1 (not used)
000 ;unused
на CTRL
флаги после загрузки F32
: F72
F72 = 0001 11 11 01 110010
0 ;Don't allow invalid numbers
1 ;Allow denormals (very small numbers)
0 ;Don't allow divide by zero
0 ;Don't allow overflow
1 ;Allow underflow
1 ;Allow inexact precision
1 ;reserved exception mask
0 ;unused
11 ;Precision Control - 11B (Double Extended Precision - 64 bits)
11 ;Rounding control
1 ;Infinity control - 1 (not used)
00011 ;unused
все, что делает процессор, включает зарезервированный, неиспользуемый бит маски.
RaiseLastFloatingPointError ()
если вы собираетесь разрабатывать программы для Windows, вам действительно нужно принять тот факт, что исключения с плавающей запятой должны быть замаскированы процессором, означает, что вы должны следить за ними сами. Как Win32Check
или RaiseLastWin32Error
, мы хотели бы RaiseLastFPError
. Лучшее, что я могу придумать-это:
procedure RaiseLastFPError();
var
statWord: Word;
const
ERROR_InvalidOperation = ;
// ERROR_Denormalized = ;
ERROR_ZeroDivide = ;
ERROR_Overflow = ;
// ERROR_Underflow = ;
// ERROR_InexactResult = ;
begin
{
Excellent reference of all the floating point instructions.
(Intel's architecture manuals have no organization whatsoever)
http://www.plantation-productions.com/Webster/www.artofasm.com/Linux/HTML/RealArithmetica2.html
Bits 0:5 are exception flags (Mask = F)
0: Invalid Operation
1: Denormalized - CPU handles correctly without a problem. Do not throw
2: Zero Divide
3: Overflow
4: Underflow - CPU handles as you'd expect. Do not throw.
5: Precision - Extraordinarily common. CPU does what you'd want. Do not throw
}
asm
fwait //Wait for pending operations
FSTSW statWord //Store floating point flags in AX.
//Waits for pending operations. (Use FNSTSW AX to not wait.)
fclex //clear all exception bits the stack fault bit,
//and the busy flag in the FPU status register
end;
if (statWord and D) <> 0 then
begin
//if (statWord and ERROR_InexactResult) <> 0 then raise EInexactResult.Create(SInexactResult)
//else if (statWord and ERROR_Underflow) <> 0 then raise EUnderflow.Create(SUnderflow)}
if (statWord and ERROR_Overflow) <> 0 then raise EOverflow.Create(SOverflow)
else if (statWord and ERROR_ZeroDivide) <> 0 then raise EZeroDivide.Create(SZeroDivide)
//else if (statWord and ERROR_Denormalized) <> 0 then raise EUnderflow.Create(SUnderflow)
else if (statWord and ERROR_InvalidOperation) <> 0 then raise EInvalidOp.Create(SInvalidOp);
end;
end;
воспроизводимый случай!
я нашел случай, когда контрольное слово с плавающей запятой Delphi по умолчанию было причиной недопустимое исключение с плавающей запятой (хотя я никогда не видел его раньше, потому что он был замаскирован). Теперь я вижу, почему это происходит! И это воспроизводимо:
procedure TForm1.Button1Click(Sender: TObject);
var
d: Real;
dover: Int64 absolute d;
begin
d := 1.35715152325557E020;
// dOver := 1d6db44ff62b68; //1.35715152325557E020
d := Round(d); //<--floating point exception
Self.Caption := FloatToStr(d);
end;
видно, что ST0
register содержит допустимое значение с плавающей запятой. Управляющее слово с плавающей запятой -72
. Там флаг исключения с плавающей запятой все ясно:
и затем, как только он выполняется, это недопустимая операция:
-
IE
(недопустимая операция) установлен флаг -
ES
(Исключение) установлен флаг
у меня было искушение задать этот вопрос как другой, но это был бы тот же самый вопрос-за исключением этого вызова Round()
.
2 ответов
проблема возникает в другом месте. Когда ваш код вводит Trunc
управляющее слово имеет значение 7F
который, IIRC, контрольное слово Windows по умолчанию. Все исключения замаскированы. Это проблема, потому что RTL Delphi ожидает, что исключения будут разоблачены.
и посмотрите на окно FPU, конечно, есть ошибки. Установлены флаги IE и PE. Это IE, который считается. Это означает, что ранее в кодовой последовательности был маскированный недопустимый операция.
тогда вы звоните Trunc
который изменяет управляющее слово, чтобы разоблачить исключения. Посмотрите на второй скриншот окна FPU. IE-1, но IM-0. Итак, бум, более раннее исключение поднято, и вы вынуждены думать, что это была ошибка Trunc
. Но это было не так.
вам нужно будет отследить резервную копию стека вызовов, чтобы узнать, почему управляющее слово не то, что должно быть в программе Delphi. Это должно быть 32
. Скорее всего, вы звоните в некоторые сторонняя библиотека, которая изменяет управляющее Слово и не восстанавливает его. Вам нужно будет найти виновника и взять на себя ответственность, когда любые вызовы этой функции возвращаются.
как только вы получите контрольное слово под контролем, вы найдете реальную причину этого исключения. Очевидно, что существует незаконная операция FP. Как только управляющее слово разоблачает исключения, ошибка будет поднята в нужной точке.
обратите внимание, что нет ничего, чтобы волноваться о несоответствии между 72
и 32
или F72
и F32
. Это просто странность с CTRL
контрольное слово, что некоторые из байтов зарезервированы и игнорируют ваши увещевания, чтобы очистить их.
ваше последнее обновление по существу задает другой вопрос. Он спрашивает об исключении, вызванном этим кодом:
procedure foo;
var
d: Real;
i: Int64;
begin
d := 1.35715152325557E020;
i := Round(d);
end;
этот код терпит неудачу, потому что задание Round()
, чтобы d
до ближайшего Int64
значение. Но ваша ценность d
больше, чем максимально возможное значение, которое может быть сохранено в Int64
и, следовательно, ловушки с плавающей запятой.