From 101dbec760a33745f90fcc0f16effd87b581ca67 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 15 Sep 2025 16:10:01 +0200 Subject: [PATCH] Scalar operators --- Src/Data/Myc.Data.Decimal.pas | 76 ++++ Src/Data/Myc.Data.Scalar.pas | 718 ++++++++++++++++++++++++++++++++++ 2 files changed, 794 insertions(+) diff --git a/Src/Data/Myc.Data.Decimal.pas b/Src/Data/Myc.Data.Decimal.pas index 151cbe1..8b8b9b5 100644 --- a/Src/Data/Myc.Data.Decimal.pas +++ b/Src/Data/Myc.Data.Decimal.pas @@ -20,6 +20,13 @@ type class operator Equal(const A, B: TDecimal): Boolean; inline; class operator NotEqual(const A, B: TDecimal): Boolean; inline; + class operator GreaterThan(const A, B: TDecimal): Boolean; inline; + class operator GreaterThanOrEqual(const A, B: TDecimal): Boolean; inline; + class operator LessThan(const A, B: TDecimal): Boolean; inline; + class operator LessThanOrEqual(const A, B: TDecimal): Boolean; inline; + class operator Negative(const A: TDecimal): TDecimal; inline; + class operator Round(const A: TDecimal): TDecimal; + class operator Trunc(const A: TDecimal): TDecimal; class operator Implicit(const A: Int64): TDecimal; inline; class operator Explicit(const A: TDecimal): Double; @@ -246,6 +253,75 @@ begin Result := not (A = B); end; +class operator TDecimal.GreaterThan(const A, B: TDecimal): Boolean; +begin + var aScale := A.GetScale; + var bScale := B.GetScale; + + // Fast path for identical scales + if aScale = bScale then + begin + Result := (A.GetValue > B.GetValue); + exit; + end; + + // Slower path: align scales before comparing values + if aScale > bScale then + begin + var scaledB := TDecimal.Create(B, aScale); + Result := (A.GetValue > scaledB.GetValue); + end + else // bScale > aScale + begin + var scaledA := TDecimal.Create(A, bScale); + Result := (scaledA.GetValue > B.GetValue); + end; +end; + +class operator TDecimal.GreaterThanOrEqual(const A, B: TDecimal): Boolean; +begin + // Reuse existing operators for consistency + Result := not (A < B); +end; + +class operator TDecimal.LessThan(const A, B: TDecimal): Boolean; +begin + // Reuse existing operators for consistency + Result := (B > A); +end; + +class operator TDecimal.LessThanOrEqual(const A, B: TDecimal): Boolean; +begin + // Reuse existing operators for consistency + Result := not (A > B); +end; + +class operator TDecimal.Negative(const A: TDecimal): TDecimal; +begin + // Negate the value, keep the scale + Result := TDecimal.Create(-A.GetValue, A.GetScale); +end; + +class operator TDecimal.Round(const A: TDecimal): TDecimal; +var + value: Int64; + divisor: Int64; +begin + // Rounds to the nearest whole number (scale 0) using arithmetic rounding. + value := A.GetValue; + divisor := PowersOf10[A.GetScale]; + if value >= 0 then + Result := TDecimal.Create((value + divisor div 2) div divisor, 0) + else + Result := TDecimal.Create((value - divisor div 2) div divisor, 0); +end; + +class operator TDecimal.Trunc(const A: TDecimal): TDecimal; +begin + // Truncates the fractional part, resulting in a decimal with scale 0 + Result := TDecimal.Create(A.GetValue div PowersOf10[A.GetScale], 0); +end; + class operator TDecimal.Implicit(const A: Int64): TDecimal; begin // Correctly create a TDecimal with scale 0 diff --git a/Src/Data/Myc.Data.Scalar.pas b/Src/Data/Myc.Data.Scalar.pas index 63fe17d..b42933d 100644 --- a/Src/Data/Myc.Data.Scalar.pas +++ b/Src/Data/Myc.Data.Scalar.pas @@ -93,6 +93,22 @@ type class function StringToKind(const AName: string): TScalarKind; static; function ToString: String; + + class operator Add(const A, B: TScalar): TScalar; + class operator Divide(const A, B: TScalar): TScalar; + class operator Equal(const A, B: TScalar): Boolean; + + //TODO implementieren + 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 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; TScalarKindHelper = record helper for TScalarKind @@ -443,6 +459,708 @@ begin end; end; +class operator TScalar.Add(const A, B: TScalar): TScalar; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + Result.Kind := A.Kind; + case A.Kind of + skInteger: Result.Value.AsInteger := A.Value.AsInteger + B.Value.AsInteger; + skInt64: Result.Value.AsInt64 := A.Value.AsInt64 + B.Value.AsInt64; + skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 + B.Value.AsUInt64; + skSingle: Result.Value.AsSingle := A.Value.AsSingle + B.Value.AsSingle; + skDouble: Result.Value.AsDouble := A.Value.AsDouble + B.Value.AsDouble; + skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal + B.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Operator Add not supported for type %s', [A.Kind.ToString]); + end; + exit; + end; + + // Slow path for mixed types with compatible conversion + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const ORDINAL_KINDS: set of TScalarKind = [skInteger, skInt64, skUInt64]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + ORDINAL_KINDS + [skDecimal]; + + if not (((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) + or ((A.Kind = skDateTime) and (B.Kind in ORDINAL_KINDS + FLOAT_KINDS)) + or ((B.Kind = skDateTime) and (A.Kind in ORDINAL_KINDS + FLOAT_KINDS))) then + raise EArgumentException.CreateFmt('Operator Add requires compatible types, but got %s and %s', [A.Kind.ToString, B.Kind.ToString]); + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then + raise EArgumentException + .CreateFmt('Operator Add cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); + + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + end; + Result := TScalar.FromDecimal(valA + valB); + end + else if (A.Kind = skDateTime) or (B.Kind = skDateTime) then + begin + if A.Kind = skDateTime then + begin + var dtPart := A.Value.AsDateTime; + var numPart: Double := 0; + case B.Kind of + skInteger: numPart := B.Value.AsInteger; + skInt64: numPart := B.Value.AsInt64; + skUInt64: numPart := B.Value.AsUInt64; + skSingle: numPart := B.Value.AsSingle; + skDouble: numPart := B.Value.AsDouble; + end; + Result := TScalar.FromDateTime(dtPart + numPart); + end + else + begin + var dtPart := B.Value.AsDateTime; + var numPart: Double := 0; + case A.Kind of + skInteger: numPart := A.Value.AsInteger; + skInt64: numPart := A.Value.AsInt64; + skUInt64: numPart := A.Value.AsUInt64; + skSingle: numPart := A.Value.AsSingle; + skDouble: numPart := A.Value.AsDouble; + end; + Result := TScalar.FromDateTime(dtPart + numPart); + end; + end + else if (A.Kind = skDouble) or (B.Kind = skDouble) then + begin + var dblA: Double := 0; + var dblB: Double := 0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := TScalar.FromDouble(dblA + dblB); + end + else if (A.Kind = skSingle) or (B.Kind = skSingle) then + begin + var sngA: Single := 0; + var sngB: Single := 0; + case A.Kind of + skInteger: sngA := A.Value.AsInteger; + skInt64: sngA := A.Value.AsInt64; + skUInt64: sngA := A.Value.AsUInt64; + skSingle: sngA := A.Value.AsSingle; + end; + case B.Kind of + skInteger: sngB := B.Value.AsInteger; + skInt64: sngB := B.Value.AsInt64; + skUInt64: sngB := B.Value.AsUInt64; + skSingle: sngB := B.Value.AsSingle; + end; + Result := TScalar.FromSingle(sngA + sngB); + end + else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then + begin + var u64A: UInt64 := 0; + var u64B: UInt64 := 0; + case A.Kind of + skInteger: u64A := UInt64(A.Value.AsInteger); + skInt64: u64A := UInt64(A.Value.AsInt64); + skUInt64: u64A := A.Value.AsUInt64; + end; + case B.Kind of + skInteger: u64B := UInt64(B.Value.AsInteger); + skInt64: u64B := UInt64(B.Value.AsInt64); + skUInt64: u64B := B.Value.AsUInt64; + end; + Result := TScalar.FromUInt64(u64A + u64B); + end + else if (A.Kind = skInt64) or (B.Kind = skInt64) then + begin + var i64A: Int64 := 0; + var i64B: Int64 := 0; + case A.Kind of + skInteger: i64A := A.Value.AsInteger; + skInt64: i64A := A.Value.AsInt64; + end; + case B.Kind of + skInteger: i64B := B.Value.AsInteger; + skInt64: i64B := B.Value.AsInt64; + end; + Result := TScalar.FromInt64(i64A + i64B); + end; +end; + +class operator TScalar.Divide(const A, B: TScalar): TScalar; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + case A.Kind of + skInteger: Result := TScalar.FromDouble(A.Value.AsInteger / B.Value.AsInteger); + skInt64: Result := TScalar.FromDouble(A.Value.AsInt64 / B.Value.AsInt64); + skUInt64: Result := TScalar.FromDouble(A.Value.AsUInt64 / B.Value.AsUInt64); + skSingle: Result := TScalar.FromSingle(A.Value.AsSingle / B.Value.AsSingle); + skDouble: Result := TScalar.FromDouble(A.Value.AsDouble / B.Value.AsDouble); + skDecimal: Result := TScalar.FromDecimal(A.Value.AsDecimal / B.Value.AsDecimal); + else + raise EArgumentException.CreateFmt('Operator Divide not supported for type %s', [A.Kind.ToString]); + end; + exit; + end; + + // Slow path for mixed types + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; + + if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then + raise EArgumentException.CreateFmt('Operator Divide not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then + raise EArgumentException + .CreateFmt('Operator Divide cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + end; + Result := TScalar.FromDecimal(valA / valB); + end + else + begin + var dblA: Double := 0.0; + var dblB: Double := 0.0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := TScalar.FromDouble(dblA / dblB); + end; +end; + +class operator TScalar.Equal(const A, B: TScalar): Boolean; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + case A.Kind of + skInteger: Result := (A.Value.AsInteger = B.Value.AsInteger); + skInt64: Result := (A.Value.AsInt64 = B.Value.AsInt64); + skUInt64: Result := (A.Value.AsUInt64 = B.Value.AsUInt64); + skSingle: Result := (A.Value.AsSingle = B.Value.AsSingle); + skDouble: Result := (A.Value.AsDouble = B.Value.AsDouble); + skDateTime: Result := (A.Value.AsDateTime = B.Value.AsDateTime); + skTimestamp: + Result := (A.Value.AsTimestamp.Time = B.Value.AsTimestamp.Time) and (A.Value.AsTimestamp.Date = B.Value.AsTimestamp.Date); + skBoolean: Result := (A.Value.AsBoolean = B.Value.AsBoolean); + skChar: Result := (A.Value.AsChar = B.Value.AsChar); + skPChar: Result := (StrComp(PChar(@A.Value.AsPChar), PChar(@B.Value.AsPChar)) = 0); + skString: Result := (A.Value.AsString = B.Value.AsString); + skBytes: Result := CompareMem(@A.Value.AsBytes, @B.Value.AsBytes, SizeOf(TScalarBytes)); + skDecimal: Result := (A.Value.AsDecimal = B.Value.AsDecimal); + else + Result := False; + end; + exit; + end; + + // Slow path for mixed, but compatible, types + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; + + if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then + begin + Result := False; + exit; + end; + + if ((A.Kind = skDecimal) and (B.Kind in FLOAT_KINDS)) or ((B.Kind = skDecimal) and (A.Kind in FLOAT_KINDS)) then + begin + Result := False; + exit; + end; + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + else + Result := False; + exit; + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + else + Result := False; + exit; + end; + Result := (valA = valB); + end + else + begin + var dblA: Double := 0.0; + var dblB: Double := 0.0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := (dblA = dblB); + end; +end; + +class operator TScalar.GreaterThan(const A, B: TScalar): Boolean; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + case A.Kind of + skInteger: Result := (A.Value.AsInteger > B.Value.AsInteger); + skInt64: Result := (A.Value.AsInt64 > B.Value.AsInt64); + skUInt64: Result := (A.Value.AsUInt64 > B.Value.AsUInt64); + skSingle: Result := (A.Value.AsSingle > B.Value.AsSingle); + skDouble: Result := (A.Value.AsDouble > B.Value.AsDouble); + skDateTime: Result := (A.Value.AsDateTime > B.Value.AsDateTime); + skDecimal: Result := (A.Value.AsDecimal > B.Value.AsDecimal); + skChar: Result := (A.Value.AsChar > B.Value.AsChar); + skString: Result := (A.Value.AsString > B.Value.AsString); + else + raise EArgumentException.CreateFmt('Operator GreaterThan not supported for type %s', [A.Kind.ToString]); + end; + exit; + end; + + // Slow path for mixed, but compatible, types + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; + + if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then + raise EArgumentException.CreateFmt('Cannot compare %s and %s', [A.Kind.ToString, B.Kind.ToString]); + + if ((A.Kind = skDecimal) and (B.Kind in FLOAT_KINDS)) or ((B.Kind = skDecimal) and (A.Kind in FLOAT_KINDS)) then + raise EArgumentException.CreateFmt('Cannot compare Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Internal error comparing %s', [A.Kind.ToString]); + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Internal error comparing %s', [B.Kind.ToString]); + end; + Result := (valA > valB); + end + else + begin + var dblA, dblB: Double; + dblA := 0.0; + dblB := 0.0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := (dblA > dblB); + end; +end; + +class operator TScalar.GreaterThanOrEqual(const A, B: TScalar): Boolean; +begin + Result := (A > B) or (A = B); +end; + +class operator TScalar.LessThan(const A, B: TScalar): Boolean; +begin + // Re-use GreaterThan logic to avoid code duplication + Result := B > A; +end; + +class operator TScalar.LessThanOrEqual(const A, B: TScalar): Boolean; +begin + Result := (A < B) or (A = B); +end; + +class operator TScalar.Multiply(const A, B: TScalar): TScalar; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + Result.Kind := A.Kind; + case A.Kind of + skInteger: Result.Value.AsInteger := A.Value.AsInteger * B.Value.AsInteger; + skInt64: Result.Value.AsInt64 := A.Value.AsInt64 * B.Value.AsInt64; + skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 * B.Value.AsUInt64; + skSingle: Result.Value.AsSingle := A.Value.AsSingle * B.Value.AsSingle; + skDouble: Result.Value.AsDouble := A.Value.AsDouble * B.Value.AsDouble; + skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal * B.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Operator Multiply not supported for type %s', [A.Kind.ToString]); + end; + exit; + end; + + // Slow path for mixed types + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + [skInteger, skInt64, skUInt64, skDecimal]; + + if not ((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) then + raise EArgumentException.CreateFmt('Operator Multiply not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then + raise EArgumentException + .CreateFmt('Operator Multiply cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + end; + Result := TScalar.FromDecimal(valA * valB); + end + else if (A.Kind = skDouble) or (B.Kind = skDouble) then + begin + var dblA: Double := 0.0; + var dblB: Double := 0.0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := TScalar.FromDouble(dblA * dblB); + end + else if (A.Kind = skSingle) or (B.Kind = skSingle) then + begin + var sngA: Single := 0.0; + var sngB: Single := 0.0; + case A.Kind of + skInteger: sngA := A.Value.AsInteger; + skInt64: sngA := A.Value.AsInt64; + skUInt64: sngA := A.Value.AsUInt64; + skSingle: sngA := A.Value.AsSingle; + end; + case B.Kind of + skInteger: sngB := B.Value.AsInteger; + skInt64: sngB := B.Value.AsInt64; + skUInt64: sngB := B.Value.AsUInt64; + skSingle: sngB := B.Value.AsSingle; + end; + Result := TScalar.FromSingle(sngA * sngB); + end + else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then + begin + var u64A: UInt64 := 0; + var u64B: UInt64 := 0; + case A.Kind of + skInteger: u64A := UInt64(A.Value.AsInteger); + skInt64: u64A := UInt64(A.Value.AsInt64); + skUInt64: u64A := A.Value.AsUInt64; + end; + case B.Kind of + skInteger: u64B := UInt64(B.Value.AsInteger); + skInt64: u64B := UInt64(B.Value.AsInt64); + skUInt64: u64B := B.Value.AsUInt64; + end; + Result := TScalar.FromUInt64(u64A * u64B); + end + else if (A.Kind = skInt64) or (B.Kind = skInt64) then + begin + var i64A: Int64 := 0; + var i64B: Int64 := 0; + case A.Kind of + skInteger: i64A := A.Value.AsInteger; + skInt64: i64A := A.Value.AsInt64; + end; + case B.Kind of + skInteger: i64B := B.Value.AsInteger; + skInt64: i64B := B.Value.AsInt64; + end; + Result := TScalar.FromInt64(i64A * i64B); + end + else + begin + Result := TScalar.FromInteger(A.Value.AsInteger * B.Value.AsInteger); + end; +end; + +class operator TScalar.Negative(const A: TScalar): TScalar; +begin + Result.Kind := A.Kind; + case A.Kind of + skInteger: Result.Value.AsInteger := -A.Value.AsInteger; + skInt64: Result.Value.AsInt64 := -A.Value.AsInt64; + skSingle: Result.Value.AsSingle := -A.Value.AsSingle; + skDouble: Result.Value.AsDouble := -A.Value.AsDouble; + skDecimal: Result.Value.AsDecimal := -A.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Operator Negative not supported for type %s', [A.Kind.ToString]); + 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 + case A.Kind of + skSingle: Result := TScalar.FromInt64(System.Round(A.Value.AsSingle)); + skDouble: Result := TScalar.FromInt64(System.Round(A.Value.AsDouble)); + skDateTime: Result := TScalar.FromInt64(System.Round(A.Value.AsDateTime)); + else + raise EArgumentException.CreateFmt('Operator Round not supported for type %s', [A.Kind.ToString]); + end; +end; + +class operator TScalar.Subtract(const A, B: TScalar): TScalar; +begin + // Fast path for identical types + if (A.Kind = B.Kind) then + begin + if (A.Kind = skDateTime) then + begin + Result := TScalar.FromDouble(A.Value.AsDateTime - B.Value.AsDateTime); + exit; + end; + Result.Kind := A.Kind; + case A.Kind of + skInteger: Result.Value.AsInteger := A.Value.AsInteger - B.Value.AsInteger; + skInt64: Result.Value.AsInt64 := A.Value.AsInt64 - B.Value.AsInt64; + skUInt64: Result.Value.AsUInt64 := A.Value.AsUInt64 - B.Value.AsUInt64; + skSingle: Result.Value.AsSingle := A.Value.AsSingle - B.Value.AsSingle; + skDouble: Result.Value.AsDouble := A.Value.AsDouble - B.Value.AsDouble; + skDecimal: Result.Value.AsDecimal := A.Value.AsDecimal - B.Value.AsDecimal; + else + raise EArgumentException.CreateFmt('Operator Subtract not supported for type %s', [A.Kind.ToString]); + end; + exit; + end; + + // Slow path for mixed types + const FLOAT_KINDS: set of TScalarKind = [skSingle, skDouble]; + const ORDINAL_KINDS: set of TScalarKind = [skInteger, skInt64, skUInt64]; + const NUMERIC_KINDS: set of TScalarKind = FLOAT_KINDS + ORDINAL_KINDS + [skDecimal]; + + if not (((A.Kind in NUMERIC_KINDS) and (B.Kind in NUMERIC_KINDS)) + or ((A.Kind = skDateTime) and (B.Kind in NUMERIC_KINDS + [skDateTime]))) then + raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); + + if (A.Kind = skDecimal) or (B.Kind = skDecimal) then + begin + if (A.Kind in FLOAT_KINDS) or (B.Kind in FLOAT_KINDS) then + raise EArgumentException + .CreateFmt('Operator Subtract cannot mix Decimal and floating-point types (%s, %s)', [A.Kind.ToString, B.Kind.ToString]); + var valA, valB: TDecimal; + case A.Kind of + skInteger: valA := TDecimal.Create(A.Value.AsInteger, 0); + skInt64: valA := TDecimal.Create(A.Value.AsInt64, 0); + skUInt64: valA := TDecimal.Create(Int64(A.Value.AsUInt64), 0); + skDecimal: valA := A.Value.AsDecimal; + end; + case B.Kind of + skInteger: valB := TDecimal.Create(B.Value.AsInteger, 0); + skInt64: valB := TDecimal.Create(B.Value.AsInt64, 0); + skUInt64: valB := TDecimal.Create(Int64(B.Value.AsUInt64), 0); + skDecimal: valB := B.Value.AsDecimal; + end; + Result := TScalar.FromDecimal(valA - valB); + end + else if (A.Kind = skDateTime) then + begin + var numPart: Double := 0.0; + case B.Kind of + skInteger: numPart := B.Value.AsInteger; + skInt64: numPart := B.Value.AsInt64; + skUInt64: numPart := B.Value.AsUInt64; + skSingle: numPart := B.Value.AsSingle; + skDouble: numPart := B.Value.AsDouble; + skDateTime: numPart := B.Value.AsDateTime; + end; + if B.Kind = skDateTime then + Result := TScalar.FromDouble(A.Value.AsDateTime - numPart) + else + Result := TScalar.FromDateTime(A.Value.AsDateTime - numPart); + end + else if (B.Kind = skDateTime) then + begin + raise EArgumentException.CreateFmt('Operator Subtract not supported for types %s and %s', [A.Kind.ToString, B.Kind.ToString]); + end + else if (A.Kind = skDouble) or (B.Kind = skDouble) then + begin + var dblA: Double := 0.0; + var dblB: Double := 0.0; + case A.Kind of + skInteger: dblA := A.Value.AsInteger; + skInt64: dblA := A.Value.AsInt64; + skUInt64: dblA := A.Value.AsUInt64; + skSingle: dblA := A.Value.AsSingle; + skDouble: dblA := A.Value.AsDouble; + end; + case B.Kind of + skInteger: dblB := B.Value.AsInteger; + skInt64: dblB := B.Value.AsInt64; + skUInt64: dblB := B.Value.AsUInt64; + skSingle: dblB := B.Value.AsSingle; + skDouble: dblB := B.Value.AsDouble; + end; + Result := TScalar.FromDouble(dblA - dblB); + end + else if (A.Kind = skSingle) or (B.Kind = skSingle) then + begin + var sngA: Single := 0.0; + var sngB: Single := 0.0; + case A.Kind of + skInteger: sngA := A.Value.AsInteger; + skInt64: sngA := A.Value.AsInt64; + skUInt64: sngA := A.Value.AsUInt64; + skSingle: sngA := A.Value.AsSingle; + end; + case B.Kind of + skInteger: sngB := B.Value.AsInteger; + skInt64: sngB := B.Value.AsInt64; + skUInt64: sngB := B.Value.AsUInt64; + skSingle: sngB := B.Value.AsSingle; + end; + Result := TScalar.FromSingle(sngA - sngB); + end + else if (A.Kind = skUInt64) or (B.Kind = skUInt64) then + begin + var u64A: UInt64 := 0; + var u64B: UInt64 := 0; + case A.Kind of + skInteger: u64A := UInt64(A.Value.AsInteger); + skInt64: u64A := UInt64(A.Value.AsInt64); + skUInt64: u64A := A.Value.AsUInt64; + end; + case B.Kind of + skInteger: u64B := UInt64(B.Value.AsInteger); + skInt64: u64B := UInt64(B.Value.AsInt64); + skUInt64: u64B := B.Value.AsUInt64; + end; + Result := TScalar.FromUInt64(u64A - u64B); + end + else if (A.Kind = skInt64) or (B.Kind = skInt64) then + begin + var i64A: Int64 := 0; + var i64B: Int64 := 0; + case A.Kind of + skInteger: i64A := A.Value.AsInteger; + skInt64: i64A := A.Value.AsInt64; + end; + case B.Kind of + skInteger: i64B := B.Value.AsInteger; + skInt64: i64B := B.Value.AsInt64; + end; + Result := TScalar.FromInt64(i64A - i64B); + end + else + begin + Result := TScalar.FromInteger(A.Value.AsInteger - B.Value.AsInteger); + end; +end; + +class operator TScalar.Trunc(const A: TScalar): TScalar; +begin + case A.Kind of + skSingle: Result := TScalar.FromInt64(System.Trunc(A.Value.AsSingle)); + skDouble: Result := TScalar.FromInt64(System.Trunc(A.Value.AsDouble)); + skDateTime: Result := TScalar.FromInt64(System.Trunc(A.Value.AsDateTime)); + else + raise EArgumentException.CreateFmt('Operator Trunc not supported for type %s', [A.Kind.ToString]); + end; +end; + { TScalarArray } constructor TScalarArray.Create(AKind: TScalarKind; const AItems: TArray);