@@ -18,13 +18,16 @@ import (
1818 "cmp"
1919 "errors"
2020 "fmt"
21+ "math"
22+ "strconv"
2123 "strings"
2224 "unicode"
2325
2426 "github.com/google/cql/internal/convert"
2527 "github.com/google/cql/model"
2628 "github.com/google/cql/result"
2729 "github.com/google/cql/types"
30+ "github.com/google/cql/ucum"
2831)
2932
3033// COMPARISON OPERATORS - https://cql.hl7.org/09-b-cqlreference.html#comparison-operators-4
@@ -36,9 +39,51 @@ func (i *interpreter) evalEqual(_ model.IBinaryExpression, lObj, rObj result.Val
3639 if result .IsNull (lObj ) || result .IsNull (rObj ) {
3740 return result .New (nil )
3841 }
42+
43+ // Special handling for Quantity types with different units
44+ _ , lIsQuantity := lObj .GolangValue ().(result.Quantity )
45+ _ , rIsQuantity := rObj .GolangValue ().(result.Quantity )
46+ if lIsQuantity && rIsQuantity {
47+ return evalEqualQuantity (nil , lObj , rObj )
48+ }
49+
3950 return result .New (lObj .Equal (rObj ))
4051}
4152
53+ // =(left Quantity, right Quantity) Boolean
54+ // https://cql.hl7.org/09-b-cqlreference.html#equal
55+ // If either unit is invalid, returns null.
56+ func evalEqualQuantity (_ model.IBinaryExpression , lObj , rObj result.Value ) (result.Value , error ) {
57+ if result .IsNull (lObj ) || result .IsNull (rObj ) {
58+ return result .New (nil )
59+ }
60+ l , r , err := applyToValues (lObj , rObj , result .ToQuantity )
61+ if err != nil {
62+ return result.Value {}, err
63+ }
64+ // If units are the same, compare values directly
65+ if l .Unit == r .Unit {
66+ return result .New (l .Value == r .Value )
67+ }
68+
69+ // If units are different, try to convert
70+ fromVal := l .Value
71+ fromUnit := string (l .Unit )
72+ toUnit := string (r .Unit )
73+
74+ // Try to convert left value to right unit for comparison.
75+ // It's not clear if this should return null for all failures here, for incompatible DateTime
76+ // unites this is true, but for other units it may not be.
77+ convertedVal , err := ucum .ConvertUnit (fromVal , fromUnit , toUnit )
78+ if err != nil {
79+ return result .New (nil )
80+ }
81+
82+ // Compare with converted value using epsilon comparison for floating point values.
83+ const epsilon = 1e-10
84+ return result .New (math .Abs (convertedVal - r .Value ) < epsilon )
85+ }
86+
4287// =(left DateTime, right DateTime) Boolean
4388// =(left Date, right Date) Boolean
4489// https://cql.hl7.org/09-b-cqlreference.html#equal
@@ -183,6 +228,33 @@ func (i *interpreter) evalEquivalentList(_ model.IBinaryExpression, lObj, rObj r
183228 return result .New (true )
184229}
185230
231+ // getDecimalPrecision returns the number of significant digits after the decimal point.
232+ // It trims trailing zeros according to the CQL specification.
233+ func getDecimalPrecision (value float64 ) int {
234+ // Convert to string to determine precision.
235+ str := strconv .FormatFloat (value , 'f' , - 1 , 64 )
236+
237+ // Find the decimal point.
238+ decimalPos := strings .IndexRune (str , '.' )
239+ if decimalPos == - 1 {
240+ return 0
241+ }
242+
243+ // Extract the decimal part and trim trailing zeros.
244+ decimalPart := strings .TrimRight (str [decimalPos + 1 :], "0" )
245+ return len (decimalPart )
246+ }
247+
248+ // roundToDecimalPlaces rounds a float64 to the specified number of decimal places.
249+ func roundToDecimalPlaces (num float64 , places int ) float64 {
250+ if places <= 0 {
251+ return math .Round (num )
252+ }
253+
254+ factor := math .Pow (10 , float64 (places ))
255+ return math .Round (num * factor ) / factor
256+ }
257+
186258// ~(left String, right String) Boolean
187259// https://cql.hl7.org/09-b-cqlreference.html#equivalent
188260func evalEquivalentString (_ model.IBinaryExpression , lObj , rObj result.Value ) (result.Value , error ) {
@@ -213,6 +285,41 @@ func equivalentString(input string) string {
213285 return out .String ()
214286}
215287
288+ // ~(left Quantity, right Quantity) Boolean
289+ // https://cql.hl7.org/09-b-cqlreference.html#equivalent
290+ func evalEquivalentQuantity (_ model.IBinaryExpression , lObj , rObj result.Value ) (result.Value , error ) {
291+ if result .IsNull (lObj ) && result .IsNull (rObj ) {
292+ return result .New (true )
293+ }
294+ if result .IsNull (lObj ) != result .IsNull (rObj ) {
295+ return result .New (false )
296+ }
297+
298+ l , r , err := applyToValues (lObj , rObj , result .ToQuantity )
299+ if err != nil {
300+ return result.Value {}, err
301+ }
302+
303+ // If units are the same, compare values directly.
304+ if l .Unit == r .Unit {
305+ return result .New (l .Value == r .Value )
306+ }
307+
308+ // Try to convert l to r's unit for comparison.
309+ fromVal := l .Value
310+ fromUnit := string (l .Unit )
311+ toUnit := string (r .Unit )
312+
313+ // Convert left value to right unit.
314+ convertedVal , err := ucum .ConvertUnit (fromVal , fromUnit , toUnit )
315+ if err != nil {
316+ return result .New (nil )
317+ }
318+ // Compare with converted value using epsilon comparison.
319+ const epsilon = 1e-10
320+ return result .New (math .Abs (convertedVal - r .Value ) < epsilon )
321+ }
322+
216323// ~(left Interval<T>, right Interval<T>) Boolean
217324// https://cql.hl7.org/09-b-cqlreference.html#equivalent-1
218325func (i * interpreter ) evalEquivalentInterval (_ model.IBinaryExpression , lObj , rObj result.Value ) (result.Value , error ) {
@@ -457,3 +564,37 @@ func compare[n cmp.Ordered](m model.IBinaryExpression, l, r n) (result.Value, er
457564 }
458565 return result.Value {}, fmt .Errorf ("internal error - unsupported Binary Comparison Expression %v" , m )
459566}
567+
568+ // op(left Quantity, right Quantity) Boolean
569+ // https://cql.hl7.org/09-b-cqlreference.html#less
570+ // https://cql.hl7.org/09-b-cqlreference.html#less-or-equal
571+ // https://cql.hl7.org/09-b-cqlreference.html#greater
572+ // https://cql.hl7.org/09-b-cqlreference.html#greater-or-equal
573+ func evalCompareQuantity (m model.IBinaryExpression , lObj , rObj result.Value ) (result.Value , error ) {
574+ if result .IsNull (lObj ) || result .IsNull (rObj ) {
575+ return result .New (nil )
576+ }
577+
578+ l , r , err := applyToValues (lObj , rObj , result .ToQuantity )
579+ if err != nil {
580+ return result.Value {}, err
581+ }
582+
583+ // If units are the same, compare values directly.
584+ // In the future we should calculate the smaller unit instead.
585+ if l .Unit == r .Unit {
586+ return compare (m , l .Value , r .Value )
587+ }
588+
589+ // If units are different, try to convert.
590+ fromVal := l .Value
591+ fromUnit := string (l .Unit )
592+ toUnit := string (r .Unit )
593+
594+ // Try to convert left value to right unit for comparison.
595+ convertedVal , err := ucum .ConvertUnit (fromVal , fromUnit , toUnit )
596+ if err != nil {
597+ return result .New (nil )
598+ }
599+ return compare (m , convertedVal , r .Value )
600+ }
0 commit comments