Тип фиксированной точки не умножается правильно
Я новичок в Ada и опробовал типы "Дельта" с фиксированной точкой. В частности, я создал 32-разрядный Дельта-тип диапазона 0.0 .. 1.0. Однако, когда я пытаюсь выровнять некоторые значения, я получаю CONSTRAINT_ERROR. Насколько я знаю, этого не должно произойти с моим заданным диапазоном. Порог для этой ошибки, по-видимому, равен sqrt(1/2)
. Я использую GNAT от MinGW-w64 версии 4.8.0.
Тестовый код (весь он компилируется в виде gnatmake <file>
без предупреждения / ошибки):
.объявления:
pragma Ada_2012;
with Ada.Unchecked_Conversion;
with Ada.Text_IO;
package Types is
type Fixed_Type is delta 1.0 / 2**32 range 0.0 .. 1.0
with Size => 32;
type Modular_Type is mod 2**32
with Size => 32;
function Fixed_To_Mod is new Ada.Unchecked_Conversion(Fixed_Type, Modular_Type);
package MIO is new Ada.Text_IO.Modular_IO(Modular_Type);
package FIO is new Ada.Text_IO.Fixed_IO(Fixed_Type);
end Types;
Особенности.АБР:
pragma Ada_2012;
with Ada.Text_IO;
with Types; use Types;
procedure Specifics is
package TIO renames Ada.Text_IO;
procedure TestValue(val: in Fixed_Type) is
square : Fixed_Type;
begin
square := val * val;
TIO.Put_Line("Value " & Fixed_Type'Image(val) & " squares properly.");
TIO.Put_Line("Square: " & Fixed_Type'Image(square));
TIO.New_Line;
exception
when Constraint_Error =>
TIO.Put_Line("Value " & Fixed_Type'Image(val) & " does not square properly.");
TIO.Put_Line("Square: " & Fixed_Type'Image(val * val));
TIO.Put_Line("Not sure how that worked.");
TIO.New_Line;
end TestValue;
function ParseFixed(s: in String; last: in Natural; val: out Fixed_Type) return Boolean is
l : Natural;
begin
FIO.Get(s(s'First..last), val, l);
return TRUE;
exception
when others =>
TIO.Put_Line("Parsing failed.");
return FALSE;
end ParseFixed;
buffer : String(1..20);
last : Natural;
f : Fixed_Type;
begin
loop
TIO.Put(">>> ");
TIO.Get_Line(buffer, last);
exit when buffer(1..last) = "quit";
if ParseFixed(buffer, last, f) then
TestValue(f);
end if;
end loop;
end Specifics;
Вывод специфики.АБР:
>>> 0.1
Value 0.1000000001 squares properly.
Square: 0.0100000000
>>> 0.2
Value 0.2000000000 squares properly.
Square: 0.0399999998
>>> 0.4
Value 0.3999999999 squares properly.
Square: 0.1599999999
>>> 0.6
Value 0.6000000001 squares properly.
Square: 0.3600000001
>>> 0.7
Value 0.7000000000 squares properly.
Square: 0.4899999998
>>> 0.75
Value 0.7500000000 does not square properly.
Square: -0.4375000000
Not sure how that worked.
>>> quit
Каким-то образом умножение val
само по себе дало отрицательное число, которое объясняет CONSTRAINT_ERROR... но неважно, почему я получаю отрицательное число в первую очередь?
Затем я решил проверить точку, в которой квадратура чисел начала давать сбои, поэтому я написал следующее фрагмент:
Fixedpointtest.АБР:
pragma Ada_2012;
with Ada.Text_IO;
with Types; use Types;
procedure FixedPointTest is
package TIO renames Ada.Text_IO;
test, square : Fixed_Type := 0.0;
begin
while test /= Fixed_Type'Last loop
square := test * test;
test := test + Fixed_Type'Delta;
end loop;
exception
when Constraint_Error =>
TIO.Put_Line("Last valid value: " & Fixed_Type'Image(test-Fixed_Type'Delta));
TIO.Put("Hex value: ");
MIO.Put(Item => Fixed_To_Mod(test-Fixed_Type'Delta), Base => 16);
TIO.New_Line;
TIO.Put("Binary value: ");
MIO.Put(Item => Fixed_To_Mod(test-Fixed_Type'Delta), Base => 2);
TIO.New_Line;
TIO.New_Line;
TIO.Put_Line("First invalid value: " & Fixed_Type'Image(test));
TIO.Put("Hex value: ");
MIO.Put(Item => Fixed_To_Mod(test), Base => 16);
TIO.New_Line;
TIO.Put("Binary value: ");
MIO.Put(Item => Fixed_To_Mod(test), Base => 2);
TIO.New_Line;
TIO.New_Line;
end FixedPointTest;
И получил следующий результат:
Last valid value: 0.7071067810
Hex value: 16#B504F333#
Binary value: 2#10110101000001001111001100110011#
First invalid value: 0.7071067812
Hex value: 16#B504F334#
Binary value: 2#10110101000001001111001100110100#
Так sqrt(1/2)
, мы встретимся снова. Не мог бы кто-нибудь объяснить мне, почему мой код делает это? Есть ли способ заставить его размножаться должным образом?
1 ответ:
Я думаю, что вы просите на 1 бит больше точности, чем на самом деле доступно "под капотом".
Ваше заявление
type Fixed_Type is delta 1.0 / 2**32 range 0.0 .. 1.0 with Size => 32;
Принимается только потому, что GNAT использовал предвзятое представление; там нет места для бита знака. Вы можете видеть это, потому что
0.7071067810
представляется как16#B504F333#
, с наиболее значимым набором битов. Итак, когда вы умножаете 0.71 на 0.71, результат имеет самый значительный бит; и низкоуровневый код думает, что это должен быть знаковый бит, поэтому мы имеем переполнение.Если вы объявите
Fixed_Type
какtype Fixed_Type is delta 1.0 / 2**31 range 0.0 .. 1.0 with Size => 32;
Все должно быть хорошо.
Еще один момент: в своем отчете о поведении
specifics
с входным значением 0,75 вы цитируете результат>>> 0.75 Value 0.7500000000 does not square properly. Square: -0.4375000000 Not sure how that worked.
Я перестроил с
gnatmake specifics.adb -g -gnato -bargs -E
, и результат теперь>>> 0.75 Value 0.7500000000 does not square properly. Execution terminated by unhandled exception Exception name: CONSTRAINT_ERROR Message: 64-bit arithmetic overflow Call stack traceback locations: 0x100020b79 0x10000ea80 0x100003520 0x100003912 0x10000143e
И обратный путь расшифровывается как
system__arith_64__raise_error (in specifics) (s-arit64.adb:364) __gnat_mulv64 (in specifics) (s-arit64.adb:318) specifics__testvalue.2581 (in specifics) (specifics.adb:20) <<<<<<<<<< _ada_specifics (in specifics) (specifics.adb:45) main (in specifics) (b~specifics.adb:246)
И
specifics.adb:20
являетсяTIO.Put_Line("Square: " & Fixed_Type'Image(val * val));
В обработчике исключений, который снова включает проблемный квадрат (не очень хорошая вещь, чтобы сделать в обработчике исключений). Вы можете смотрите, что значение
0.75
было напечатано без каких-либо проблем в строке выше: и вfixedpointtest.adb
не было никаких проблем в добавлениях, ведущих к последнему допустимому значению0.7071067810
.Я был весьма удивлен, обнаружив, что
-gnato
обнаруживает эту ошибку, поскольку я думал, что она применима только к целочисленной арифметике; но на самом деле в руководстве пользователя GNAT говорится, что она применима и к арифметике с фиксированной точкой. Оказывается, что вы можете избежать ошибки ограничения и получить правильный арифметический результат с помощью-gnato3
:Но только ценой использования произвольной арифметики с множественной точностью-не очень хорошая идея для системы с ограниченным временем!>>> 0.75 Value 0.7500000000 squares properly. Square: 0.5625000000