ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/branches/udr/client/FBNumeric.pas
Revision: 371
Committed: Wed Jan 5 15:21:22 2022 UTC (2 years, 10 months ago) by tony
Content type: text/x-pascal
File size: 11581 byte(s)
Log Message:
Beta Release 0.1

File Contents

# User Rev Content
1 tony 371 (*
2     * Firebird Interface (fbintf). The fbintf components provide a set of
3     * Pascal language bindings for the Firebird API. Although predominantly
4     * a new development they include source code taken from IBX and may be
5     * considered a derived product. This software thus also includes the copyright
6     * notice and license conditions from IBX.
7     *
8     * Except for those parts dervied from IBX, contents of this file are subject
9     * to the Initial Developer's Public License Version 1.0 (the "License"); you
10     * may not use this file except in compliance with the License. You may obtain a
11     * copy of the License here:
12     *
13     * http://www.firebirdsql.org/index.php?op=doc&id=idpl
14     *
15     * Software distributed under the License is distributed on an "AS
16     * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
17     * implied. See the License for the specific language governing rights
18     * and limitations under the License.
19     *
20     * The Initial Developer of the Original Code is Tony Whyman.
21     *
22     * The Original Code is (C) 2021 Tony Whyman, MWA Software
23     * (http://www.mwasoftware.co.uk).
24     *
25     * All Rights Reserved.
26     *
27     * Contributor(s): ______________________________________.
28     *
29     *)
30     unit FBNumeric;
31    
32     {$IFDEF MSWINDOWS}
33     {$DEFINE WINDOWS}
34     {$ENDIF}
35    
36     {$IFDEF FPC}
37     {$mode delphi}
38     {$codepage UTF8}
39     {$interfaces COM}
40     {$ENDIF}
41    
42     interface
43    
44     uses
45     Classes, SysUtils, IB, FBActivityMonitor, FmtBCD;
46    
47     { The IFBNumeric interface is a managed type to hold a fixed point integer
48     as a 64-bit signed integer plus a scale factor. i.e. the scale factor is
49     the base 10 exponent in
50    
51     Fixed Point Value = <64-bit signed integer> * 10^<scale factor>
52    
53     }
54    
55     function NewNumeric(aValue: AnsiString): IFBNumeric; overload;
56     function NewNumeric(aValue: double; aScale: integer): IFBNumeric; overload;
57     function NewNumeric(aValue: TBCD): IFBNumeric; overload;
58     function NewNumeric(aValue: currency): IFBNumeric; overload;
59     function NewNumeric(aValue: Int64): IFBNumeric; overload;
60     function NumericFromRawValues(aValue: Int64; aScale: integer): IFBNumeric;
61    
62     function TryStrToNumeric(S: Ansistring; out Value: int64; out scale: integer): boolean;
63     function NumericToDouble(aValue: Int64; aScale: integer): double;
64    
65     function SafeSmallInt(aValue: Int64): Smallint;
66     function SafeInteger(aValue: Int64): integer;
67    
68    
69     implementation
70    
71     uses IBUtils, FBMessages, Math;
72    
73     type
74    
75     { TIBNumeric }
76    
77     { TFBNumeric }
78    
79     TFBNumeric = class(TFBInterfacedObject,IFBNumeric)
80     private
81     FValue: Int64;
82     FScale: integer;
83     // function AdjustScaleFromCurrency(Value: Currency; aScale: Integer): Int64;
84     public
85     constructor Create(aValue: Int64; aScale: integer); overload;
86     constructor Create(aValue: Int64); overload;
87     constructor Create(aValue: AnsiString); overload;
88     constructor Create(aValue: double; aScale: integer); overload;
89     constructor Create(aValue: Currency); overload;
90     constructor Create(aValue: TBCD); overload;
91     public
92     {IFBNumeric}
93     function getRawValue: Int64;
94     function getScale: integer;
95     function clone(aNewScale: integer): IFBNumeric;
96     function getAsString: AnsiString;
97     function getAsDouble: double;
98     function getAsCurrency: Currency;
99     function getAsBCD: TBCD;
100     function getAsInt64: Int64; {scaled}
101     function getAsInteger: integer; {scaled - may be truncated}
102     function getAsSmallInt: SmallInt; {scaled - may be truncated}
103     end;
104    
105     function NewNumeric(aValue: AnsiString): IFBNumeric;
106     begin
107     Result := TFBNumeric.Create(aValue);
108     end;
109    
110     function NewNumeric(aValue: double; aScale: integer): IFBNumeric;
111     begin
112     Result := TFBNumeric.Create(aValue,aScale);
113     end;
114    
115     function NewNumeric(aValue: TBCD): IFBNumeric;
116     begin
117     Result := TFBNumeric.Create(aValue);
118     end;
119    
120     function NewNumeric(aValue: currency): IFBNumeric;
121     begin
122     Result := TFBNumeric.Create(aValue);
123     end;
124    
125     function NewNumeric(aValue: Int64): IFBNumeric;
126     begin
127     Result := TFBNumeric.Create(aValue);
128     end;
129    
130     function NumericFromRawValues(aValue: Int64; aScale: integer): IFBNumeric;
131     begin
132     Result := TFBNumeric.Create(aValue,aScale);
133     end;
134    
135     function TryStrToNumeric(S: Ansistring; out Value: int64; out scale: integer): boolean;
136     var i: integer;
137     ds: integer;
138     exponent: integer;
139     begin
140     Result := false;
141     ds := 0;
142     exponent := 0;
143     S := Trim(S);
144     Value := 0;
145     scale := 0;
146     if Length(S) = 0 then
147     Exit;
148     {$IF declared(DefaultFormatSettings)}
149     with DefaultFormatSettings do
150     {$ELSE}
151     {$IF declared(FormatSettings)}
152     with FormatSettings do
153     {$IFEND}
154     {$IFEND}
155     begin
156     for i := length(S) downto 1 do
157     begin
158     if S[i] = AnsiChar(DecimalSeparator) then
159     begin
160     if ds <> 0 then Exit; {only one allowed}
161     ds := i;
162     dec(exponent);
163     system.Delete(S,i,1);
164     end
165     else
166     if S[i] in ['+','-'] then
167     begin
168     if (i > 1) and not (S[i-1] in ['e','E']) then
169     Exit; {malformed}
170     end
171     else
172     if S[i] in ['e','E'] then {scientific notation}
173     begin
174     if ds <> 0 then Exit; {not permitted in exponent}
175     if exponent <> 0 then Exit; {only one allowed}
176     exponent := i;
177     end
178     else
179     if not (S[i] in ['0'..'9']) then
180     {Note: ThousandSeparator not allowed by Delphi specs}
181     Exit; {bad character}
182     end;
183    
184     if exponent > 0 then
185     begin
186     Result := TryStrToInt(system.copy(S,exponent+1,maxint),Scale);
187     if Result then
188     begin
189     {adjust scale for decimal point}
190     if ds <> 0 then
191     Scale := Scale - (exponent - ds);
192     Result := TryStrToInt64(system.copy(S,1,exponent-1),Value);
193     end;
194     end
195     else
196     begin
197     if ds > 0 then
198     begin
199     {remove trailing zeroes}
200     while (ds < length(S)) and (S[length(S)] = '0') do {no need to check length as ds > 0}
201     system.delete(S,Length(S),1);
202     scale := ds - Length(S) - 1;
203     end;
204     Result := TryStrToInt64(S,Value);
205     end;
206     end;
207     end;
208    
209     function NumericToDouble(aValue: Int64; aScale: integer): double;
210     begin
211     Result := aValue * IntPower(10,aScale);
212     end;
213    
214     function SafeSmallInt(aValue: Int64): Smallint;
215     begin
216     if aValue > High(smallint) then
217     IBError(ibxeIntegerOverflow,[]);
218     if aValue < Low(smallint) then
219     IBError(ibxIntegerUnderflow,[]);
220     Result := aValue;
221     end;
222    
223     function SafeInteger(aValue: Int64): integer;
224     begin
225     if aValue > High(integer) then
226     IBError(ibxeIntegerOverflow,[]);
227     if aValue < Low(integer) then
228     IBError(ibxIntegerUnderflow,[]);
229     Result := aValue;
230     end;
231    
232     { TFBNumeric }
233    
234     (*function TFBNumeric.AdjustScaleFromCurrency(Value: Currency; aScale: Integer
235     ): Int64;
236     var
237     Scaling : Int64;
238     i : Integer;
239     begin
240     Result := 0;
241     Scaling := 1;
242     if aScale < 0 then
243     begin
244     for i := -1 downto aScale do
245     Scaling := Scaling * 10;
246     result := trunc(Value * Scaling);
247     end
248     else
249     if aScale > 0 then
250     begin
251     for i := 1 to aScale do
252     Scaling := Scaling * 10;
253     result := trunc(Value / Scaling);
254     end
255     else
256     result := trunc(Value);
257     end;*)
258    
259     constructor TFBNumeric.Create(aValue: Int64; aScale: integer);
260     begin
261     inherited Create;
262     FValue := aValue;
263     FScale := aScale;
264     end;
265    
266     constructor TFBNumeric.Create(aValue: Int64);
267     begin
268     inherited Create;
269     FValue := aValue;
270     FScale := 0;
271     end;
272    
273     constructor TFBNumeric.Create(aValue: AnsiString);
274     begin
275     inherited Create;
276     if not TryStrToNumeric(aValue,FValue,FScale) then
277     IBError(ibxeInvalidDataConversion,[aValue]);
278     end;
279    
280     constructor TFBNumeric.Create(aValue: double; aScale: integer);
281     var
282     Scaling : Int64;
283     i : Integer;
284     begin
285     inherited Create;
286     FScale := aScale;
287     FValue := 0;
288     Scaling := 1;
289     if aScale < 0 then
290     begin
291     for i := -1 downto aScale do
292     Scaling := Scaling * 10;
293     FValue := trunc(aValue * Scaling);
294     end
295     else
296     if aScale > 0 then
297     begin
298     for i := 1 to aScale do
299     Scaling := Scaling * 10;
300     FValue := trunc(aValue / Scaling);
301     end
302     else
303     FValue := trunc(aValue);
304     // writeln('Adjusted ',Value,' to ',Result);
305     end;
306    
307     constructor TFBNumeric.Create(aValue: Currency);
308     begin
309     inherited Create;
310     Move(aValue,FValue,sizeof(Int64));
311     FScale := -4;
312     end;
313    
314     constructor TFBNumeric.Create(aValue: TBCD);
315     var ScaledBCD: TBCD;
316     begin
317     inherited Create;
318     FScale := -BCDScale(aValue);
319     BCDMultiply(aValue,Power(10,-FScale),ScaledBCD);
320     FValue := BCDToInteger(ScaledBCD,true);
321     end;
322    
323     function TFBNumeric.getRawValue: Int64;
324     begin
325     Result := FValue;
326     end;
327    
328     function TFBNumeric.getScale: integer;
329     begin
330     Result := FScale;
331     end;
332    
333     function TFBNumeric.clone(aNewScale: integer): IFBNumeric;
334     begin
335     Result := TFBNumeric.Create(Round(FValue * IntPower(10,FScale-aNewScale)),aNewScale);
336     end;
337    
338     function TFBNumeric.getAsString: AnsiString;
339     var Scaling : AnsiString;
340     i: Integer;
341     begin
342     Result := IntToStr(FValue);
343     Scaling := '';
344     if FScale > 0 then
345     begin
346     for i := 1 to FScale do
347     Result := Result + '0';
348     end
349     else
350     if FScale < 0 then
351     {$IF declared(DefaultFormatSettings)}
352     with DefaultFormatSettings do
353     {$ELSE}
354     {$IF declared(FormatSettings)}
355     with FormatSettings do
356     {$IFEND}
357     {$IFEND}
358     begin
359     if Length(Result) > -FScale then
360     system.Insert(DecimalSeparator,Result,Length(Result) + FScale+1)
361     else
362     begin
363     Scaling := '0' + DecimalSeparator;
364     for i := -1 downto FScale + Length(Result) do
365     Scaling := Scaling + '0';
366     if FValue < 0 then
367     system.insert(Scaling,Result,2)
368     else
369     Result := Scaling + Result;
370     end;
371     end;
372     end;
373    
374     function TFBNumeric.getAsDouble: double;
375     begin
376     Result := NumericToDouble(FValue,FScale);
377     end;
378    
379     function TFBNumeric.getAsCurrency: Currency;
380     var value: int64;
381     begin
382     if FScale <> -4 then
383     begin
384     value := clone(-4).GetRawValue;
385     Move(value,Result,sizeof(Currency));
386     end
387     else
388     Move(FValue,Result,sizeof(Currency));
389     end;
390    
391     (*var
392     Scaling : Int64;
393     i : Integer;
394     FractionText, PadText, CurrText: AnsiString;
395     begin
396     Result := 0;
397     Scaling := 1;
398     PadText := '';
399     if FScale > 0 then
400     begin
401     for i := 1 to FScale do
402     Scaling := Scaling * 10;
403     result := FValue * Scaling;
404     end
405     else
406     if FScale < 0 then
407     begin
408     for i := -1 downto FScale do
409     Scaling := Scaling * 10;
410     FractionText := IntToStr(abs(FValue mod Scaling));
411     for i := Length(FractionText) to -FScale -1 do
412     PadText := '0' + PadText;
413     {$IF declared(DefaultFormatSettings)}
414     with DefaultFormatSettings do
415     {$ELSE}
416     {$IF declared(FormatSettings)}
417     with FormatSettings do
418     {$IFEND}
419     {$IFEND}
420     if FValue < 0 then
421     CurrText := '-' + IntToStr(Abs(FValue div Scaling)) + DecimalSeparator + PadText + FractionText
422     else
423     CurrText := IntToStr(Abs(FValue div Scaling)) + DecimalSeparator + PadText + FractionText;
424     try
425     result := StrToCurr(CurrText);
426     except
427     on E: Exception do
428     IBError(ibxeInvalidDataConversion, [nil]);
429     end;
430     end
431     else
432     result := FValue;
433     end; *)
434    
435     function TFBNumeric.getAsBCD: TBCD;
436     begin
437     Result := DoubleToBCD(getAsDouble);
438     end;
439    
440     function TFBNumeric.getAsInt64: Int64;
441     var
442     Scaling : Int64;
443     i: Integer;
444     Val: Int64;
445     begin
446     Scaling := 1;
447     Val := FValue;
448     if FScale > 0 then begin
449     for i := 1 to FScale do Scaling := Scaling * 10;
450     result := Val * Scaling;
451     end else if FScale < 0 then begin
452     for i := -1 downto FScale do Scaling := Scaling * 10;
453     result := Val div Scaling;
454     end else
455     result := Val;
456     end;
457    
458     function TFBNumeric.getAsInteger: integer;
459     begin
460     Result := SafeInteger(getAsInt64);
461     end;
462    
463     function TFBNumeric.getAsSmallInt: SmallInt;
464     begin
465     Result := SafeSmallInt(getAsInt64);
466     end;
467    
468     end.
469