#1 2014-01-08 09:08:24

yurasek
Member
From: Belarus
Registered: 2011-04-19
Posts: 18

Double_Precision

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

#2 2014-01-08 17:34:48

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,659
Website

Re: Double_Precision

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

Board footer

Powered by FluxBB