Simon Willison uses Claude to cut SQLite number parsing from 67 to 19 ns
An amount such as “-$1,234.56” in a CSV file needs cleaning before SQLite can add it up. Simon Willison asked Claude to compare C and Rust extensions for the task: the initial Rust versions were faster, although their binaries were considerably larger. After replacing the standard parser in C, processing time per row on mixed data fell from 67 to 19 ns, versus 41–46 ns for Rust.
AI-processed from Simon Willison; edited by Hamidun News
You export payments to CSV, load them into SQLite, and find “-$1,234.56” in the amounts column. To a person, that is a number; to the database, it is a string that needs parsing before addition and sorting. Simon Willison decided to investigate how difficult it would be to add a parse_number() function for this purpose and asked Claude to compare implementations in C and Rust.
The result was a small but useful engineering experiment. Initially, Rust won on speed, while C produced the smaller extension. Then Willison asked for the number-parsing function in C to be replaced, and the balance shifted: processing time per row on mixed data fell from 67 to 19 nanoseconds. The practical lesson is how a single section of code can change the outcome of a language comparison.
One SQL function instead of cleanup in every query
The job of parse_number() is straightforward: remove commas, currency symbols and percent signs from a string, account for a leading minus sign, and return a numeric value. If the string cannot be parsed, the function should return NULL. This behavior allows repetitive cleanup to be moved into a SQLite extension, with the result then used in calculations.
For example, removing the dollar sign and thousands separator from “-$1,234.56” produces -1234.56. That value can then be added to other payments or sorted numerically. Developers no longer have to rebuild a chain of string replacements in every query.
Percentages need their own rules. In his original request, Willison proposed stripping the percent sign. But each product needs to decide whether “12.5%” means the number 12.5 or the fraction 0.125. The correctness of subsequent calculations depends on that decision. Fast string parsing alone cannot make it for the team.
Why Rust was initially faster
Claude prepared versions of the extension in C and using two Rust libraries: rusqlite and sqlite-loadable-rs. According to Willison, the compiled C extension was 14 KB. The Rust versions were 321 and 298 KB, respectively. For a small embedded utility, that difference can matter.
However, the first C version was slower. On mixed data, its additional processing overhead was 67 ns per row, while the Rust versions recorded 46 and 41 ns. Willison attributed the difference to number parsing: the mechanism used in Rust was faster than the standard C function strtod.
The next request to Claude was more focused: write a custom decimal-number parser in C that could match or beat Rust. After that replacement, the C version recorded 19 ns on mixed data, 16 ns on integers and 18 ns on decimal fractions.
For comparison, on decimal fractions the original C version required 106 ns, while the Rust versions took 55 and 47 ns. All the measurements cited come from an experiment involving two million rows. They measure additional processing overhead per row, so these figures cannot be translated directly into execution times for an entire SQL query.
What to check before using it
The experiment shows why choosing a language based on a single benchmark can easily lead to a hasty decision. Here, replacing one function changed the result substantially. For your own workload, it is more useful to identify the expensive operation first, then compare alternatives using the same data and the same requirements for the output.
The speedup alone does not prove that a custom parser can fully replace the standard one. Before using it, check supported formats, very large values, rounding, whitespace and strings containing junk. If a product accepts amounts from different countries, it will also need rules for decimal commas and thousands separators. These are areas to verify in the implementation, not established defects in the published code.
Willison also asked Claude to document the research and published the result in the simonw/research repository. For developers, it provides a starting point: repeat the measurements on your own export, check correctness, and only then choose an implementation. In this case, Claude helped move quickly from an idea to several measured alternatives; their suitability still has to be assessed against the requirements of the particular system.
- Nov 9, 2026robots.txt Doesn't Block AI Training: Which Bots to Block to Stay in AI Answers
- Nov 9, 2026Automattic Launches Spacefast: Hosting with a Database and Auth for Vibe Coding
- Nov 9, 2026Dokumenterra team tasks Claude with writing automated tests in five hours
- Nov 9, 2026Simon Willison collapses AI-generated PR descriptions: the changes in 1 paragraph
Key Questions Answered
What are the key details on One SQL function instead of cleanup in every query?
The job of parse_number() is straightforward: remove commas, currency symbols and percent signs from a string, account for a leading minus sign, and return a numeric value. If the string cannot be parsed, the function should return NULL. This behavior allows repetitive cleanup to be moved into a SQLite extension, with the result then used in calculations. For example, removing the dollar sign and thousands separator from “-$1,234.56” produces -1234.56.
Why Rust was initially faster?
Claude prepared versions of the extension in C and using two Rust libraries: rusqlite and sqlite-loadable-rs. According to Willison, the compiled C extension was 14 KB. The Rust versions were 321 and 298 KB, respectively. For a small embedded utility, that difference can matter. However, the first C version was slower. On mixed data, its additional processing overhead was 67 ns per row, while the Rust versions recorded 46 and 41 ns.
What to check before using it?
The experiment shows why choosing a language based on a single benchmark can easily lead to a hasty decision. Here, replacing one function changed the result substantially. For your own workload, it is more useful to identify the expensive operation first, then compare alternatives using the same data and the same requirements for the output. The speedup alone does not prove that a custom parser can fully replace the standard one.
Test Frontier LLMs & Autonomous Agents in One Workspace
Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.
The AI world, distilled — once a week
Seven stories that actually mattered, hand-picked. No noise, no reposts, no press releases.
Done! Check your inbox for a confirmation.