Опять плавающая запятая

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Опять плавающая запятая

Сообщение Sharfik » 13.05.2020 23:04:07

..смайлик с дергающимся глазом нужен.

Число 42,3 считывается из XML и записывается в Variant как Double, и еще раз записывается в переменную типа Double.
В одном случае это при исполнении 42,299999999997, а в другом 42,300000000004 при просмотре через отладчик.
Ладно, допустим. Но стоит закрыть "проект" в исполняемой программе, открыть его заново любое кол-во раз.. и эти числа везде становятся одинаковыми. :?

Далее...та же программа.
В одном месте SimpleRoundTo делает так
Код: Выделить всё
//SizeDout:=42,299999999997
SizeA     :=SimpleRoundTo(SizeDout/2,-2);
//SizeA     :=21,15000000001


В другом месте SimpleRoundTo делает так. И это никак не связано с делением
Код: Выделить всё
//SizeDout:=27,00000000001
SizeA     :=SimpleRoundTo(SizeDout,-2);
//SizeA     :=27


И еще мне непонятно, почему где то вообще просто 21,37 без кучи нулей выводится.
Sharfik
энтузиаст
 
Сообщения: 531
Зарегистрирован: 20.07.2013 01:04:30

Re: Опять плавающая запятая

Сообщение wavebvg » 14.05.2020 00:21:57

А в чем проблема? На уровне погрешности так работает FPU и ничего особенного в этом нет. Используйте типы большей точности (к примеру, extended) или организуйте расчеты требуемой вами точности на CPU, реализовав/взяв готовые алгоритмы/библиотеки, к примеру из этой статьи: http://freepascal.ru/article/freepascal/20190411080000/
wavebvg
постоялец
 
Сообщения: 294
Зарегистрирован: 28.02.2008 04:57:35

Re: Опять плавающая запятая

Сообщение Sharfik » 14.05.2020 02:16:22

На оборот. Extended мне не нужен как страшный сон. Double насколько я помню не должен себя так вести, а без проблем сравнить значение на уровне 2х знаков после запятой и все. Ну хотя бы тот же Round должен возвращать два знака после запятой, а не эту ересь. И тем более, перезагрузка данных не должна влиять на алгоритмы сравнения, а тут получается что все плавает плюс минус километр.
Sharfik
энтузиаст
 
Сообщения: 531
Зарегистрирован: 20.07.2013 01:04:30

Re: Опять плавающая запятая

Сообщение zub » 14.05.2020 03:28:13

21,15 не имеет точного представления в плавающей запятой, поэтому это чтото очень рядом, но не ровно 21,15. Хоть зароундись
zub
долгожитель
 
Сообщения: 2553
Зарегистрирован: 14.11.2005 23:51:26

Re: Опять плавающая запятая

Сообщение Сквозняк » 14.05.2020 13:11:56

Нужно проверять, не используется ли где-то при преобразовании и хранении чисел другой тип переменных, тот же Extended.
Сквозняк
энтузиаст
 
Сообщения: 736
Зарегистрирован: 29.06.2006 22:08:32

Re: Опять плавающая запятая

Сообщение Sharfik » 14.05.2020 13:29:23

zub писал(а):21,15 не имеет точного представления в плавающей запятой, поэтому это чтото очень рядом, но не ровно 21,15. Хоть зароундись

Ага, только если не так глубоко смотреть - с учетом указанной переменной определенная часть должна отбрасываться и не влиять на сравнения. Иначе у типа Currency тоже большие проблемы были бы.
Сквозняк писал(а):Нужно проверять, не используется ли где-то при преобразовании и хранении чисел другой тип переменных, тот же Extended.

С утра, на свежую голову тоже так подумал. Спасибо
Sharfik
энтузиаст
 
Сообщения: 531
Зарегистрирован: 20.07.2013 01:04:30

Re: Опять плавающая запятая

Сообщение wavebvg » 14.05.2020 14:44:05

Посмотрел на свежую голову:

Число 42,3 считывается из XML и записывается в Variant как Double, и еще раз записывается в переменную типа Double.
В одном случае это при исполнении 42,299999999997, а в другом 42,300000000004 при просмотре через отладчик.


Точность Double (15-16 значащих) говорит о том, что при чтении происходит что-то ненормальное, должно быть "точнее"

Код: Выделить всё
42,299999999997
42.299999999999997


При использовании Variant, вообще такой проблемы не должно наблюдаться:

Код: Выделить всё
program Project1;

uses
  Math,
  Variants;

var
  SizeDout, SizeA: Variant;
begin
  SizeDout := Double(42.3);
  SizeA := SimpleRoundTo(Double(SizeDout) / 2, -2);
  WriteLn(SizeDout);
  WriteLn(SizeA);
  WriteLn(VarType(SizeA) and VarTypeMask);
end.


Вывод в консоль:

42.3
21.15
5


При использовании любой математики с плавающей запятой будет множественное преобразование типов с понижением точности. К примеру, в процедуре SimpleRoundTo используется возведение в степень простым умножением действительных чисел, из-за чего возникают не интуитивные результаты.
wavebvg
постоялец
 
Сообщения: 294
Зарегистрирован: 28.02.2008 04:57:35

Re: Опять плавающая запятая

Сообщение Sharfik » 16.05.2020 18:26:54

wavebvg спасибо, но у меня крыша едет именно от того что хранение идет в памяти в Variant с принудительной типизацией как Double, в функцию все загружается как Double. И когда идет деление FPC все рассчитывает у себя внутри как extended, и возвращает в переменную Double значение без округления до Extended и сравнивает (A=B) тоже не с учетом типа переменной, а как Extended.
Насколько я понял, в модуле Math есть упоминание что какого то типа данных может и не быть, т.е. для отдельных процессоров отключаемые типы. Интересно вот как ему отрубить Extended
Тыкать везде округление, и сравнивать через функцию округления или дельты не самое красивое решение.
Пока костыли:
Код: Выделить всё
function ReadNodeValueAsDouble(Node:TXMLNode;Name:ShortString):Double;
var
    ChildNode :TXMLNode;
    l         :integer;
    Val       :String;
begin
    Result    :=0;
    ChildNode :=Node.NodeByName(Name);
    Math.SetRoundMode(rmUP);
    Val       :=ChildNode.Value;
    if not TryStrToFloat(Val, Result, XMLFileDefaultFormatSettings) then
    begin
       raise Exception.Create('Error read XML as Double');
    end
    else begin
       l:=Pos(XMLFileDefaultFormatSettings.DecimalSeparator,Val);
       if l>0 then
       begin
          l      :=Length(Val)-l;           //0,174
          l      :=-1*l;
          Result :=SimpleRoundTo(Result,l); //0,1739999999...
       end;                                 //0,1740000000...
    end;
end;


Кусок кода ниже оставил для подстраховки. Пока экспериментировал, оказалось что Math.SetRoundMode(rmUP); перед TryStrToFloat достаточно чтобы получать почти то, что написано в XML.
Код: Выделить всё
       l:=Pos(XMLFileDefaultFormatSettings.DecimalSeparator,Val);
       if l>0 then
       begin
          l      :=Length(Val)-l;           //0,174
          l      :=-1*l;
          Result :=SimpleRoundTo(Result,l); //0,1739999999...
       end;
Sharfik
энтузиаст
 
Сообщения: 531
Зарегистрирован: 20.07.2013 01:04:30

Re: Опять плавающая запятая

Сообщение LearnMagic » 17.05.2020 20:57:58

Sharfik
См статью

Добавлено спустя 5 минут 4 секунды:
Sharfik писал(а):... Иначе у типа Currency тоже большие проблемы были бы.

Не совсем. Тип Currency является вещественным типом данных с фиксированной точкой (4 десятичных знака после точки), представляющий значения в диапазоне от -922337203685477.5808 до 922337203685477.5807. Тип данных Currency используется с целью получения точного результата при арифметических вычислениях
LearnMagic
новенький
 
Сообщения: 66
Зарегистрирован: 10.11.2016 23:13:38


Вернуться в Free Pascal Compiler

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

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru