@@ -81,8 +81,17 @@ struct HeapEquableValue {
8181 return false ;
8282 }
8383
84- // Otherwise fall back to string comparison
85- return _serialized == other._serialized;
84+ // Try string comparison first
85+ if (_serialized == other._serialized) {
86+ return true ;
87+ }
88+
89+ // For scalars, try numeric comparison (handles double vs int, scientific notation)
90+ if (_kind == Kind.scalar && other._kind == Kind.scalar) {
91+ return numericEquals (_serialized[], other._serialized[]);
92+ }
93+
94+ return false ;
8695 }
8796
8897 bool isEqualTo (const HeapEquableValue other) nothrow const @trusted {
@@ -96,8 +105,52 @@ struct HeapEquableValue {
96105 return false ;
97106 }
98107
99- // Otherwise fall back to string comparison
100- return _serialized == other._serialized;
108+ // Try string comparison first
109+ if (_serialized == other._serialized) {
110+ return true ;
111+ }
112+
113+ // For scalars, try numeric comparison (handles double vs int, scientific notation)
114+ if (_kind == Kind.scalar && other._kind == Kind.scalar) {
115+ return numericEquals (_serialized[], other._serialized[]);
116+ }
117+
118+ return false ;
119+ }
120+
121+ // / Compares two string representations as numbers if both are numeric.
122+ // / Uses relative epsilon comparison for floating point tolerance.
123+ private static bool numericEquals (const (char )[] a, const (char )[] b) @nogc nothrow pure @safe {
124+ bool aIsNum, bIsNum;
125+ double aVal = parseDouble(a, aIsNum);
126+ double bVal = parseDouble(b, bIsNum);
127+
128+ if (aIsNum && bIsNum) {
129+ return approxEqual (aVal, bVal);
130+ }
131+
132+ return false ;
133+ }
134+
135+ // / Approximate equality check for floating point numbers.
136+ // / Uses relative epsilon for large numbers and absolute epsilon for small numbers.
137+ private static bool approxEqual (double a, double b) @nogc nothrow pure @safe {
138+ import core.stdc.math : fabs;
139+
140+ // Handle exact equality (including infinities)
141+ if (a == b) {
142+ return true ;
143+ }
144+
145+ double diff = fabs(a - b);
146+ double larger = fabs(a) > fabs(b) ? fabs(a) : fabs(b);
147+
148+ // Use relative epsilon scaled to the magnitude of the numbers
149+ // For numbers around 1e6, epsilon of ~1e-9 relative gives ~1e-3 absolute tolerance
150+ enum double relEpsilon = 1e-9 ;
151+ enum double absEpsilon = 1e-9 ;
152+
153+ return diff <= larger * relEpsilon || diff <= absEpsilon;
101154 }
102155
103156 bool isLessThan (ref const HeapEquableValue other) @nogc nothrow const @trusted {
@@ -376,6 +429,18 @@ version (unittest) {
376429 assert (! v1.isEqualTo(v3));
377430 }
378431
432+ @(" isEqualTo handles numeric comparison for double vs int" )
433+ unittest {
434+ // 1003200.0 serialized as scientific notation vs integer
435+ auto doubleVal = HeapEquableValue.createScalar(" 1.0032e+06" );
436+ auto intVal = HeapEquableValue.createScalar(" 1003200" );
437+
438+ assert (doubleVal.kind() == HeapEquableValue.Kind.scalar);
439+ assert (intVal.kind() == HeapEquableValue.Kind.scalar);
440+ assert (doubleVal.isEqualTo(intVal), " 1.0032e+06 should equal 1003200" );
441+ assert (intVal.isEqualTo(doubleVal), " 1003200 should equal 1.0032e+06" );
442+ }
443+
379444 @(" array type stores elements" )
380445 unittest {
381446 auto arr = HeapEquableValue.createArray(" [1, 2, 3]" );
0 commit comments