unit Myc.Data.Scalar; interface uses System.SysUtils, Myc.Data.Decimal, Myc.Data.Series; {$SCOPEDENUMS ON} type // A scalar value with a type identifier. TScalar = record public type TKind = (Ordinal, Float); TValue = record case TKind of TKind.Ordinal: (AsInt64: Int64); TKind.Float: (AsDouble: Double); end; TBinaryOp = (Add, Subtract, Multiply, Divide, Equal, NotEqual, Less, Greater, LessOrEqual, GreaterOrEqual); TUnaryOp = (Negate, &Not); TKindHelper = record helper for TKind public function ToString: string; end; TBinaryOpHelper = record helper for TScalar.TBinaryOp function ToString: string; end; TUnaryOpHelper = record helper for TScalar.TUnaryOp function ToString: string; end; public Kind: TKind; Value: TValue; // Creates a new scalar from a kind and a value. constructor Create(AKind: TKind; const AValue: TValue); // Factory methods for core types. class function FromInt64(AValue: Int64): TScalar; static; inline; class function FromDouble(AValue: Double): TScalar; static; inline; // Implicit casts for core types. class operator Implicit(AValue: Int64): TScalar; overload; inline; class operator Implicit(AValue: Double): TScalar; overload; inline; class operator Implicit(const A: TScalar): Double; overload; class operator Implicit(const A: TScalar): Int64; overload; class function StringToKind(const AName: string): TKind; static; function ToString: String; class function IsBinaryOperatorSupported(Op: TBinaryOp; A, B: TKind): Boolean; static; class function IsUnaryOperatorSupported(Op: TUnaryOp; A: TKind): Boolean; static; class function TryBinaryOperation(Op: TBinaryOp; const A, B: TScalar; out Res: TScalar): Boolean; static; class function TryUnaryOperation(Op: TUnaryOp; const A: TScalar; out Res: TScalar): Boolean; static; class operator Add(const A, B: TScalar): TScalar; class operator Divide(const A, B: TScalar): TScalar; class operator Equal(const A, B: TScalar): Boolean; class operator GreaterThan(const A, B: TScalar): Boolean; class operator GreaterThanOrEqual(const A, B: TScalar): Boolean; class operator LessThan(const A, B: TScalar): Boolean; class operator LessThanOrEqual(const A, B: TScalar): Boolean; class operator LogicalNot(const A: TScalar): TScalar; class operator Multiply(const A, B: TScalar): TScalar; class operator Negative(const A: TScalar): TScalar; class operator NotEqual(const A, B: TScalar): Boolean; class operator Round(const A: TScalar): TScalar; class operator Subtract(const A, B: TScalar): TScalar; class operator Trunc(const A: TScalar): TScalar; end; // An array of scalar values of the same kind. TScalarArray = record public Kind: TScalar.TKind; Items: TArray; constructor Create(AKind: TScalar.TKind; const AItems: TArray); end; TScalarTuple = TArray; // A field definition for a scalar record. TScalarRecordField = record Name: String; Kind: TScalar.TKind; constructor Create(const AName: String; AKind: TScalar.TKind); end; TScalarRecordDefinition = record private FFields: TArray; public constructor Create(const AFields: TArray); function IndexOf(const Name: String): Integer; property Fields: TArray read FFields; end; // A record of scalar values, based on a definition. TScalarRecord = record public constructor Create(const ADef: TScalarRecordDefinition; const AFields: TArray); function GetDef: TScalarRecordDefinition; inline; function GetFields: TArray; inline; function GetItems(const Name: String): TScalar; property Def: TScalarRecordDefinition read GetDef; property Fields: TArray read GetFields; property Items[const Name: String]: TScalar read GetItems; default; strict private FDef: TScalarRecordDefinition; FFields: TArray; end; ISeries = interface {$region 'private'} function GetCount: Int64; function GetItems(Idx: Integer): TScalar; function GetTotalCount: Int64; {$endregion} property Count: Int64 read GetCount; property Items[Idx: Integer]: TScalar read GetItems; default; property TotalCount: Int64 read GetTotalCount; end; IWriteableSeries = interface(ISeries) procedure Add(const Item: TScalar.TValue; Lookback: Int64 = -1); end; // A time series of scalar records, optimized for memory and access speed. TScalarSeries = class(TInterfacedObject, ISeries, IWriteableSeries) private FKind: TScalar.TKind; FArray: TChunkArray; FTotalCount: Int64; function GetCount: Int64; inline; function GetItems(Idx: Integer): TScalar; inline; function GetTotalCount: Int64; inline; public constructor Create(AKind: TScalar.TKind); procedure Add(const Item: TScalar.TValue; Lookback: Int64 = -1); end; IRecordSeries = interface {$region 'private'} function GetCount: Int64; function GetDef: TScalarRecordDefinition; function GetItems(Idx: Integer): TScalarRecord; function GetTotalCount: Int64; {$endregion} procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1); function CreateMemberSeries(const Field: String): ISeries; property Count: Int64 read GetCount; property Def: TScalarRecordDefinition read GetDef; property Items[Idx: Integer]: TScalarRecord read GetItems; default; property TotalCount: Int64 read GetTotalCount; end; // A time series of scalar records, optimized for memory and access speed. TScalarRecordSeries = class(TInterfacedObject, IRecordSeries) type TMemberSeries = class(TInterfacedObject, ISeries) private FRecordSeries: TScalarRecordSeries; FKind: TScalar.TKind; FOffset: Integer; function GetCount: Int64; function GetItems(Idx: Integer): TScalar; function GetTotalCount: Int64; public constructor Create(ARecordSeries: TScalarRecordSeries; AElementIdx: Integer); destructor Destroy; override; end; private FDef: TScalarRecordDefinition; FArray: TChunkArray; FTotalCount: Int64; function GetCount: Int64; inline; function GetDef: TScalarRecordDefinition; inline; function GetItems(Idx: Integer): TScalarRecord; inline; function GetTotalCount: Int64; inline; function GetItemRef(Idx: Integer): TChunkArray.PT; inline; public constructor Create(const ADef: TScalarRecordDefinition); procedure Add(const Item: TScalarRecord; Lookback: Int64 = -1); function CreateMemberSeries(const Field: String): ISeries; property Count: Int64 read GetCount; property Def: TScalarRecordDefinition read GetDef; property Items[Idx: Integer]: TScalarRecord read GetItems; default; property TotalCount: Int64 read GetTotalCount; end; TIndexSeries = class(TInterfacedObject, ISeries) private FIndexArray: TArray; function GetCount: Int64; function GetTotalCount: Int64; function GetItems(Idx: Integer): TScalar; public constructor Create(const AIndexArray: TArray); end; implementation { TScalar } constructor TScalar.Create(AKind: TKind; const AValue: TValue); begin Kind := AKind; Value := AValue; end; class function TScalar.FromDouble(AValue: Double): TScalar; begin Result.Kind := TKind.Float; Result.Value.AsDouble := AValue; end; class operator TScalar.Implicit(AValue: Double): TScalar; begin Result.Kind := TKind.Float; Result.Value.AsDouble := AValue; end; class operator TScalar.Implicit(AValue: Int64): TScalar; begin Result.Kind := TKind.Ordinal; Result.Value.AsInt64 := AValue; end; class operator TScalar.Implicit(const A: TScalar): Double; begin if A.Kind = TKind.Ordinal then Result := A.Value.AsInt64 else Result := A.Value.AsDouble; end; class operator TScalar.Implicit(const A: TScalar): Int64; begin if A.Kind = TKind.Ordinal then Result := A.Value.AsInt64 else raise EInvalidCast.Create('Cannot implicitly convert a Float to an Int64'); end; class function TScalar.FromInt64(AValue: Int64): TScalar; begin Result.Kind := TKind.Ordinal; Result.Value.AsInt64 := AValue; end; class function TScalar.IsBinaryOperatorSupported(Op: TBinaryOp; A, B: TKind): Boolean; begin Result := True; end; class function TScalar.IsUnaryOperatorSupported(Op: TUnaryOp; A: TKind): Boolean; begin if (Op = TUnaryOp.Not) and (A = TKind.Float) then Result := False else Result := True; end; class function TScalar.StringToKind(const AName: string): TKind; begin if SameText(AName, 'Ordinal') then Result := TKind.Ordinal else if SameText(AName, 'Float') then Result := TKind.Float else raise EArgumentException.CreateFmt('Unknown scalar type name: "%s"', [AName]); end; function TScalar.ToString: String; begin case Kind of TKind.Ordinal: Result := IntToStr(Value.AsInt64); TKind.Float: Result := FloatToStr(Value.AsDouble); else Result := '[Unknown Scalar]'; end; end; class function TScalar.TryBinaryOperation(Op: TBinaryOp; const A, B: TScalar; out Res: TScalar): Boolean; begin try case Op of TBinaryOp.Add: Res := A + B; TBinaryOp.Subtract: Res := A - B; TBinaryOp.Multiply: Res := A * B; TBinaryOp.Divide: Res := A / B; TBinaryOp.Equal: Res := TScalar.FromInt64(Integer(A = B)); TBinaryOp.NotEqual: Res := TScalar.FromInt64(Integer(A <> B)); TBinaryOp.Less: Res := TScalar.FromInt64(Integer(A < B)); TBinaryOp.Greater: Res := TScalar.FromInt64(Integer(A > B)); TBinaryOp.LessOrEqual: Res := TScalar.FromInt64(Integer(A <= B)); TBinaryOp.GreaterOrEqual: Res := TScalar.FromInt64(Integer(A >= B)); else Result := False; exit; end; Result := True; except Result := False; end; end; class function TScalar.TryUnaryOperation(Op: TUnaryOp; const A: TScalar; out Res: TScalar): Boolean; begin if not IsUnaryOperatorSupported(Op, A.Kind) then begin Result := False; exit; end; try case Op of TUnaryOp.Negate: Res := -A; TUnaryOp.Not: Res := not A; else Result := False; exit; end; Result := True; except Result := False; end; end; class operator TScalar.Add(const A, B: TScalar): TScalar; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 + B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA + valB; end; end; class operator TScalar.Divide(const A, B: TScalar): TScalar; begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA / valB; end; class operator TScalar.Equal(const A, B: TScalar): Boolean; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 = B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA = valB; end; end; class operator TScalar.GreaterThan(const A, B: TScalar): Boolean; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 > B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA > valB; end; end; class operator TScalar.GreaterThanOrEqual(const A, B: TScalar): Boolean; begin Result := not (A < B); end; class operator TScalar.LessThan(const A, B: TScalar): Boolean; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 < B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA < valB; end; end; class operator TScalar.LessThanOrEqual(const A, B: TScalar): Boolean; begin Result := not (A > B); end; class operator TScalar.LogicalNot(const A: TScalar): TScalar; begin if A.Kind <> TKind.Ordinal then raise EArgumentException.Create('Operator Not not supported for type Float'); if A.Value.AsInt64 = 0 then Result := 1 else Result := 0; end; class operator TScalar.Multiply(const A, B: TScalar): TScalar; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 * B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA * valB; end; end; class operator TScalar.Negative(const A: TScalar): TScalar; begin Result.Kind := A.Kind; case A.Kind of TKind.Ordinal: Result.Value.AsInt64 := -A.Value.AsInt64; TKind.Float: Result.Value.AsDouble := -A.Value.AsDouble; end; end; class operator TScalar.NotEqual(const A, B: TScalar): Boolean; begin Result := not (A = B); end; class operator TScalar.Round(const A: TScalar): TScalar; begin var val: Double; if A.Kind = TKind.Ordinal then val := A.Value.AsInt64 else val := A.Value.AsDouble; Result := System.Round(val); end; class operator TScalar.Subtract(const A, B: TScalar): TScalar; begin if (A.Kind = TKind.Ordinal) and (B.Kind = TKind.Ordinal) then Result := A.Value.AsInt64 - B.Value.AsInt64 else begin var valA, valB: Double; if A.Kind = TKind.Ordinal then valA := A.Value.AsInt64 else valA := A.Value.AsDouble; if B.Kind = TKind.Ordinal then valB := B.Value.AsInt64 else valB := B.Value.AsDouble; Result := valA - valB; end; end; class operator TScalar.Trunc(const A: TScalar): TScalar; begin var val: Double; if A.Kind = TKind.Ordinal then val := A.Value.AsInt64 else val := A.Value.AsDouble; Result := System.Trunc(val); end; { TScalarArray } constructor TScalarArray.Create(AKind: TScalar.TKind; const AItems: TArray); begin Kind := AKind; Items := AItems; end; { TScalarRecordField } constructor TScalarRecordField.Create(const AName: String; AKind: TScalar.TKind); begin Name := AName; Kind := AKind; end; { TScalarRecord } constructor TScalarRecord.Create(const ADef: TScalarRecordDefinition; const AFields: TArray); begin Assert(Length(ADef.Fields) = Length(AFields), 'Field definition and value count must match.'); FDef := ADef; FFields := AFields; end; function TScalarRecord.GetDef: TScalarRecordDefinition; begin Result := FDef; end; function TScalarRecord.GetFields: TArray; begin Result := FFields; end; function TScalarRecord.GetItems(const Name: String): TScalar; var idx: Integer; begin idx := FDef.IndexOf(Name); if idx < 0 then raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Name]); Result.Create(FDef.Fields[idx].Kind, FFields[idx]); end; { TScalarRecordDefinition } constructor TScalarRecordDefinition.Create(const AFields: TArray); begin FFields := AFields; end; function TScalarRecordDefinition.IndexOf(const Name: String): Integer; var i: Integer; begin for i := 0 to High(FFields) do begin if (CompareText(FFields[i].Name, Name) = 0) then exit(i); end; Result := -1; end; { TScalarRecordSeries } constructor TScalarRecordSeries.Create(const ADef: TScalarRecordDefinition); begin Assert(Length(ADef.Fields) > 0); FDef := ADef; FTotalCount := 0; end; procedure TScalarRecordSeries.Add(const Item: TScalarRecord; Lookback: Int64 = -1); begin FArray.Add(Item.Fields, Length(FDef.Fields) * Lookback); inc(FTotalCount); end; function TScalarRecordSeries.CreateMemberSeries(const Field: String): ISeries; begin var elem := FDef.IndexOf(Field); if elem < 0 then raise EArgumentException.CreateFmt('Field "%s" not found in record definition.', [Field]); Result := TMemberSeries.Create(Self, elem); end; function TScalarRecordSeries.GetCount: Int64; begin Result := FArray.Count div Length(FDef.Fields); end; function TScalarRecordSeries.GetDef: TScalarRecordDefinition; begin Result := FDef; end; function TScalarRecordSeries.GetItemRef(Idx: Integer): TChunkArray.PT; begin var len := Length(FDef.Fields); Result := FArray.ItemRef[(FArray.Count - len) - (Idx * len)]; end; function TScalarRecordSeries.GetItems(Idx: Integer): TScalarRecord; var values: TArray; begin SetLength(values, Length(FDef.Fields)); Move(GetItemRef(Idx)^, values[0], sizeof(TScalar.TValue) * Length(values)); Result.Create(FDef, values); end; function TScalarRecordSeries.GetTotalCount: Int64; begin Result := FTotalCount; end; function TScalar.TKindHelper.ToString: string; begin case Self of TScalar.TKind.Ordinal: Result := 'Ordinal'; TScalar.TKind.Float: Result := 'Float'; else Result := 'unknown'; end; end; { TScalar_TBinaryOpHelper } function TScalar.TBinaryOpHelper.ToString: string; begin case Self of TScalar.TBinaryOp.Add: Result := '+'; TScalar.TBinaryOp.Subtract: Result := '-'; TScalar.TBinaryOp.Multiply: Result := '*'; TScalar.TBinaryOp.Divide: Result := '/'; TScalar.TBinaryOp.Equal: Result := '='; TScalar.TBinaryOp.NotEqual: Result := '<>'; TScalar.TBinaryOp.Less: Result := '<'; TScalar.TBinaryOp.Greater: Result := '>'; TScalar.TBinaryOp.LessOrEqual: Result := '<='; TScalar.TBinaryOp.GreaterOrEqual: Result := '>='; else Result := '?'; end; end; { TScalar_TUnaryOpHelper } function TScalar.TUnaryOpHelper.ToString: string; begin case Self of TScalar.TUnaryOp.Negate: Result := '-'; TScalar.TUnaryOp.Not: Result := 'not'; else Result := '?'; end; end; constructor TScalarRecordSeries.TMemberSeries.Create(ARecordSeries: TScalarRecordSeries; AElementIdx: Integer); begin inherited Create; FRecordSeries := ARecordSeries; FRecordSeries._AddRef; FKind := FRecordSeries.FDef.FFields[AElementIdx].Kind; FOffset := sizeof(TScalar.TValue) * AElementIdx; end; destructor TScalarRecordSeries.TMemberSeries.Destroy; begin FRecordSeries._Release; inherited; end; function TScalarRecordSeries.TMemberSeries.GetCount: Int64; begin Result := FRecordSeries.Count; end; function TScalarRecordSeries.TMemberSeries.GetItems(Idx: Integer): TScalar; var P: TChunkArray.PT; begin P := FRecordSeries.GetItemRef(Idx); inc(PByte(P), FOffset); Result.Create(FKind, P^); end; function TScalarRecordSeries.TMemberSeries.GetTotalCount: Int64; begin Result := FRecordSeries.TotalCount; end; { TScalarSeries } constructor TScalarSeries.Create(AKind: TScalar.TKind); begin FKind := AKind; FTotalCount := 0; end; procedure TScalarSeries.Add(const Item: TScalar.TValue; Lookback: Int64 = -1); begin FArray.Add(Item, Lookback); inc(FTotalCount); end; function TScalarSeries.GetCount: Int64; begin Result := FArray.Count; end; function TScalarSeries.GetItems(Idx: Integer): TScalar; begin Result.Create(FKind, FArray[FArray.Count - Idx - 1]); end; function TScalarSeries.GetTotalCount: Int64; begin Result := FTotalCount; end; { TIndexSeries } constructor TIndexSeries.Create(const AIndexArray: TArray); begin inherited Create; FIndexArray := AIndexArray; end; function TIndexSeries.GetCount: Int64; begin Result := Length(FIndexArray); end; function TIndexSeries.GetTotalCount: Int64; begin Result := GetCount; end; function TIndexSeries.GetItems(Idx: Integer): TScalar; var sourceIndex: Integer; begin sourceIndex := FIndexArray[GetCount - 1 - Idx]; Result := TScalar.FromInt64(sourceIndex); end; initialization Assert(sizeof(TScalar.TValue) = 8); end.