Scalar 64bit decimal type
This commit is contained in:
+2
-1
@@ -1,9 +1,10 @@
|
||||
unit TestAst;
|
||||
unit TestAst deprecated;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Ast,
|
||||
Myc.Ast.Evaluator,
|
||||
DUnitX.TestFramework;
|
||||
|
||||
type
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
unit Myc.Data.Decimal;
|
||||
|
||||
interface
|
||||
|
||||
{$H+}
|
||||
|
||||
type
|
||||
TScale = 0..7;
|
||||
|
||||
TDecimal64 = record
|
||||
strict private
|
||||
FValue: Int64;
|
||||
public
|
||||
constructor Create(AValue: Int64; AScale: TScale); overload;
|
||||
constructor Create(const AValue: TDecimal64; ANewScale: TScale); overload;
|
||||
|
||||
class operator Add(const A, B: TDecimal64): TDecimal64;
|
||||
class operator Subtract(const A, B: TDecimal64): TDecimal64;
|
||||
class operator Multiply(const A, B: TDecimal64): TDecimal64;
|
||||
class operator Divide(const A, B: TDecimal64): TDecimal64;
|
||||
|
||||
class operator Equal(const A, B: TDecimal64): Boolean;
|
||||
class operator NotEqual(const A, B: TDecimal64): Boolean;
|
||||
|
||||
class operator Implicit(const A: Int64): TDecimal64;
|
||||
class operator Explicit(const A: TDecimal64): Int64;
|
||||
class operator Explicit(const A: TDecimal64): Double;
|
||||
|
||||
class function MinValue(AScale: TScale): TDecimal64; static;
|
||||
class function MaxValue(AScale: TScale): TDecimal64; static;
|
||||
|
||||
function GetValue: Int64;
|
||||
function GetScale: TScale;
|
||||
function GetRawValue: Int64; inline;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math;
|
||||
|
||||
const
|
||||
SCALE_BITS = 3;
|
||||
VALUE_BITS = 61;
|
||||
SCALE_MASK = $07;
|
||||
|
||||
// Range of the 61-bit signed value part
|
||||
MIN_VALUE_61BIT = -(1 shl 60);
|
||||
MAX_VALUE_61BIT = (1 shl 60) - 1;
|
||||
|
||||
// Use a lookup table for powers of 10 for performance
|
||||
PowersOf10: array[TScale] of Int64 = (1, 10, 100, 1000, 10000, 100000, 1000000, 10000000);
|
||||
|
||||
{ TDecimal64 }
|
||||
|
||||
constructor TDecimal64.Create(AValue: Int64; AScale: TScale);
|
||||
begin
|
||||
FValue := (Int64(AScale) shl VALUE_BITS) or (AValue and ((1 shl VALUE_BITS) - 1));
|
||||
end;
|
||||
|
||||
constructor TDecimal64.Create(const AValue: TDecimal64; ANewScale: TScale);
|
||||
var
|
||||
oldScale: TScale;
|
||||
newValue: Int64;
|
||||
scaleDiff: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
// Create a new decimal by changing the scale of an existing one.
|
||||
oldScale := AValue.GetScale;
|
||||
newValue := AValue.GetValue;
|
||||
|
||||
if oldScale = ANewScale then
|
||||
begin
|
||||
FValue := AValue.FValue;
|
||||
exit;
|
||||
end;
|
||||
|
||||
scaleDiff := ANewScale - oldScale;
|
||||
if scaleDiff > 0 then
|
||||
begin
|
||||
// Increasing scale: multiply by 10^scaleDiff, checking for overflow at each step.
|
||||
for i := 1 to scaleDiff do
|
||||
begin
|
||||
if newValue > (MAX_VALUE_61BIT div 10) then
|
||||
raise EOverflow.Create('Decimal scale up resulted in an overflow.');
|
||||
if newValue < (MIN_VALUE_61BIT div 10) then
|
||||
raise EOverflow.Create('Decimal scale up resulted in an overflow.');
|
||||
newValue := newValue * 10;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Decreasing scale: divide by 10^(-scaleDiff). Overflow is not an issue here.
|
||||
newValue := newValue div PowersOf10[-scaleDiff];
|
||||
end;
|
||||
|
||||
// Pack the new value and scale
|
||||
FValue := (Int64(ANewScale) shl VALUE_BITS) or (newValue and ((1 shl VALUE_BITS) - 1));
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Add(const A, B: TDecimal64): TDecimal64;
|
||||
var
|
||||
scale: TScale;
|
||||
resultValue: Int64;
|
||||
begin
|
||||
scale := A.GetScale;
|
||||
if scale <> B.GetScale then
|
||||
raise EArgumentException.Create('Operands must have the same scale.');
|
||||
|
||||
resultValue := A.GetValue + B.GetValue;
|
||||
Result := TDecimal64.Create(resultValue, A.GetScale);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Subtract(const A, B: TDecimal64): TDecimal64;
|
||||
var
|
||||
scale: TScale;
|
||||
resultValue: Int64;
|
||||
begin
|
||||
scale := A.GetScale;
|
||||
if scale <> B.GetScale then
|
||||
raise EArgumentException.Create('Operands must have the same scale.');
|
||||
|
||||
resultValue := A.GetValue - B.GetValue;
|
||||
Result := TDecimal64.Create(resultValue, scale);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Multiply(const A, B: TDecimal64): TDecimal64;
|
||||
var
|
||||
scale: TScale;
|
||||
resultValue: Int64;
|
||||
begin
|
||||
scale := A.GetScale;
|
||||
if scale <> B.GetScale then
|
||||
raise EArgumentException.Create('Operands must have the same scale.');
|
||||
|
||||
resultValue := Trunc(A.GetValue * B.GetValue / PowersOf10[scale]);
|
||||
Result := TDecimal64.Create(resultValue, scale);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Divide(const A, B: TDecimal64): TDecimal64;
|
||||
var
|
||||
scale: TScale;
|
||||
resultValue: Int64;
|
||||
begin
|
||||
scale := A.GetScale;
|
||||
if scale <> B.GetScale then
|
||||
raise EArgumentException.Create('Operands must have the same scale.');
|
||||
|
||||
if B.GetValue = 0 then
|
||||
raise EDivByZero.Create('Division by zero');
|
||||
|
||||
// Correctly scale the dividend to preserve precision
|
||||
resultValue := Trunc(A.GetValue * PowersOf10[scale] / B.GetValue);
|
||||
Result := TDecimal64.Create(resultValue, scale);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Equal(const A, B: TDecimal64): Boolean;
|
||||
begin
|
||||
if A.GetScale <> B.GetScale then
|
||||
Result := False
|
||||
else
|
||||
Result := (A.GetValue = B.GetValue);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.NotEqual(const A, B: TDecimal64): Boolean;
|
||||
begin
|
||||
Result := not (A = B);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Implicit(const A: Int64): TDecimal64;
|
||||
begin
|
||||
// Correctly create a TDecimal64 with scale 0
|
||||
Result := TDecimal64.Create(A, 0);
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Explicit(const A: TDecimal64): Int64;
|
||||
begin
|
||||
Result := A.GetValue;
|
||||
end;
|
||||
|
||||
class operator TDecimal64.Explicit(const A: TDecimal64): Double;
|
||||
begin
|
||||
Result := A.GetValue / PowersOf10[A.GetScale];
|
||||
end;
|
||||
|
||||
class function TDecimal64.MaxValue(AScale: TScale): TDecimal64;
|
||||
begin
|
||||
Result := TDecimal64.Create(MAX_VALUE_61BIT, AScale);
|
||||
end;
|
||||
|
||||
class function TDecimal64.MinValue(AScale: TScale): TDecimal64;
|
||||
begin
|
||||
Result := TDecimal64.Create(MIN_VALUE_61BIT, AScale);
|
||||
end;
|
||||
|
||||
function TDecimal64.GetValue: Int64;
|
||||
var
|
||||
value: Int64;
|
||||
begin
|
||||
value := FValue and ((1 shl VALUE_BITS) - 1);
|
||||
// Perform sign extension from 61 to 64 bits
|
||||
if (value and (1 shl (VALUE_BITS - 1))) <> 0 then
|
||||
Result := value or (not ((1 shl VALUE_BITS) - 1))
|
||||
else
|
||||
Result := value;
|
||||
end;
|
||||
|
||||
function TDecimal64.GetScale: TScale;
|
||||
begin
|
||||
Result := TScale((FValue shr VALUE_BITS) and SCALE_MASK);
|
||||
end;
|
||||
|
||||
function TDecimal64.GetRawValue: Int64;
|
||||
begin
|
||||
Result := FValue;
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,65 @@
|
||||
unit Myc.Data.POD;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Myc.Data.Series;
|
||||
|
||||
type
|
||||
// POD: Plain old data (that fits into a 64 bit register)
|
||||
|
||||
TScalarKind = (skInteger, skInt64, skUInt64, skSingle, skDouble, skTimestamp, skBoolean, skChar, skString, skBytes);
|
||||
|
||||
TScalarValue = record
|
||||
case TScalarKind of
|
||||
skInteger: (AsInteger: Integer);
|
||||
skInt64: (AsInt64: Int64);
|
||||
skUInt64: (AsUInt64: UInt64);
|
||||
skSingle: (AsSingle: Single);
|
||||
skDouble: (AsDouble: Double);
|
||||
skTimestamp: (AsTimeStamp: TDateTime);
|
||||
skBoolean: (AsBoolean: Boolean);
|
||||
skChar: (AsChar: Char);
|
||||
skString: (AsString: String[15]);
|
||||
skBytes: (AsBytes: array[0..15] of Byte);
|
||||
end;
|
||||
|
||||
TScalar = record
|
||||
Kind: TScalarKind;
|
||||
Value: TScalarValue;
|
||||
end;
|
||||
|
||||
// Basic data structures using the scalar type (these ar not POD of course)
|
||||
|
||||
TScalarArray = record
|
||||
Kind: TScalarKind;
|
||||
Items: TArray<TScalarValue>;
|
||||
end;
|
||||
|
||||
TScalarTuple = TArray<TScalar>;
|
||||
|
||||
TScalarRecordField = record
|
||||
Name: String;
|
||||
Kind: TScalarKind;
|
||||
end;
|
||||
|
||||
TScalarRecordDefinition = record
|
||||
Fields: TArray<TScalarRecordField>;
|
||||
end;
|
||||
|
||||
TScalarRecord = record
|
||||
Def: TScalarRecordDefinition;
|
||||
Fields: TArray<TScalarValue>;
|
||||
end;
|
||||
|
||||
TScalarSeries = record
|
||||
Kind: TScalarKind;
|
||||
Items: TSeries<TScalarValue>;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
initialization
|
||||
Assert(sizeof(TScalarValue) = 16);
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user