You are not logged in.
Pages: 1
In Delphi, there are some problems that occur at operations with non-integer numbers. Here's a simple example:
procedure CheckDoublePrecision;
var
t1, t2: TDateTime;
S: String;
begin
t1:= EncodeDateTime(2014, 1, 8, 1, 0, 0, 0); //t1 = $40E455E155555555
S:= DoubleToStr(t1);
DecimalSeparator:= '.';
t2:= StrToFloat(S);//t2 = $40E455E15555555A
if t1 <> t2 then
ShowMessage('t1 <> t2');
end;
When the value DOUBLE_PRECISION = 15, then due to the nature of rounding produces a different value t1. As a solution you can increase the DOUBLE_PRECISION to 17, that would solve a number of problems:
- operations of converting a Double to a string and vice versa will occur without loss;
- Double values will be correctly stored in the database and retrieved from it.
A simple example to verify the correctness of rounding:
procedure CheckDoublePrecision2;
var
t1, t2: TDateTime;
i: Integer;
S: String;
begin
DecimalSeparator:= '.';
t1:= EncodeDateTime(2014, 1, 8, 1, 0, 0, 0);
for i:= 0 to 10000000 do
begin
S:= DoubleToStr(t1);
t2:= StrToFloat(S);
if t1 <> t2 then
begin
ShowMessage('Failed on ' + S);
Exit;
end;
Inc(PInt64(@t1)^);
end;
end;
Conceptual example of data distortion:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Mormot, mORMotSQLite3, SynSQLite3Static, DateUtils, StdCtrls;
type
TSQLSampleRecord = class(TSQLRecord)
private
fValue: Double;
published
property Value: Double read fValue write fValue;
end;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Sample: TSQLSampleRecord;
DB: TSQLRestServerDB;
t: Double;
begin
t:= EncodeDateTime(2014, 1, 8, 1, 0, 0, 0);
DB:= TSQLRestServerDB.Create(TSQLModel.Create([TSQLSampleRecord]),
ExtractFilePath(Application.ExeName) + 'sample.db', True);
try
DB.Model.Owner:= DB;
DB.CreateMissingTables(0);
DB.Delete(TSQLSampleRecord, 1);
Sample:= TSQLSampleRecord.Create;
try
Sample.ID:= 1;
Sample.Value:= t;
DB.Add(Sample, True, True);
if DB.Retrieve(1, Sample) then
begin
//now in hex
//t = $40E455E155555555
//Sample.Value = $$40E455E15555555A
if t <> Sample.Value then
ShowMessage('t and Sample.Value are different!');
end;
finally
Sample.Free;
end;
finally
DB.Free;
end;
end;
end.
Last edited by yurasek (2014-01-08 13:41:00)
Offline
Indeed.
Whenever you have a conversion to/from text (e.g. when using JSON, as we do in mORMot for transmission at ORM level), you may loose some precision.
But conversion to/from/to text will work as expected.
This is why you need to use SameValue() with a given precision everywhere, when you compare two single/double/TDateTime values for equality.
See how it is defined in SynCommons.pas:
/// compare to floating point values, with IEEE 754 double precision
// - use this function instead of raw = operator
// - the precision is calculated from the A and B value range
// - faster equivalent than SameValue() in Math unit
// - if you know the precision range of A and B, it's faster to check abs(A-B)<range
function SameValue(const A, B: Double; DoublePrec: double = 1E-12): Boolean;
This is what we do in all our units.
Offline
Pages: 1