@@ -140,10 +140,24 @@ static bool parseSingleCriterion(std::string_view token, FilterCriterion& crit)
140140 crit.target = TargetField::INFO_KEY ;
141141 }
142142
143- // Try to parse as numeric using fast charconv
143+ // Try to parse as numeric using strtod (from_chars for double unavailable on macOS)
144144 double d;
145- auto result = std::from_chars (valPart.data (), valPart.data () + valPart.size (), d);
146- if (result.ec == std::errc{} && result.ptr == valPart.data () + valPart.size ()) {
145+ char buf[64 ];
146+ bool isNumeric = false ;
147+ if (valPart.size () < sizeof (buf)) {
148+ std::memcpy (buf, valPart.data (), valPart.size ());
149+ buf[valPart.size ()] = ' \0 ' ;
150+ char * endptr;
151+ d = std::strtod (buf, &endptr);
152+ isNumeric = (endptr == buf + valPart.size ());
153+ } else {
154+ std::string tmp (valPart);
155+ char * endptr;
156+ d = std::strtod (tmp.c_str (), &endptr);
157+ isNumeric = (endptr == tmp.c_str () + tmp.size ());
158+ }
159+
160+ if (isNumeric) {
147161 crit.fieldType = FieldType::NUMERIC ;
148162 crit.numericValue = d;
149163 crit.stringValue .clear ();
@@ -253,12 +267,35 @@ bool VCFXRecordFilter::extractInfoValue(std::string_view info, std::string_view
253267}
254268
255269// ============================================================================
256- // Fast numeric parsing using std::from_chars (no exceptions, no allocations)
270+ // Fast numeric parsing - uses strtod with careful bounds checking
271+ // (std::from_chars for double is not available on macOS libc++)
257272// ============================================================================
258273bool VCFXRecordFilter::parseDouble (std::string_view sv, double & out) {
259274 if (sv.empty ()) return false ;
260- auto result = std::from_chars (sv.data (), sv.data () + sv.size (), out);
261- return result.ec == std::errc{} && result.ptr == sv.data () + sv.size ();
275+
276+ // Fast path: check if the string_view is null-terminated or followed by
277+ // a non-numeric character (common case for VCF parsing)
278+ const char * start = sv.data ();
279+ const char * end = start + sv.size ();
280+
281+ // Need a null-terminated string for strtod
282+ // Use a small stack buffer for typical numeric values
283+ char buf[64 ];
284+ if (sv.size () < sizeof (buf)) {
285+ std::memcpy (buf, start, sv.size ());
286+ buf[sv.size ()] = ' \0 ' ;
287+ start = buf;
288+ } else {
289+ // Unlikely: very long numeric string
290+ std::string tmp (sv);
291+ char * endptr;
292+ out = std::strtod (tmp.c_str (), &endptr);
293+ return endptr == tmp.c_str () + tmp.size ();
294+ }
295+
296+ char * endptr;
297+ out = std::strtod (start, &endptr);
298+ return endptr == start + sv.size ();
262299}
263300
264301bool VCFXRecordFilter::parseInt (std::string_view sv, int64_t & out) {
0 commit comments