{"record":{"id":"be90f1b016f053a4","repo":"nautechsystems/nautilus_trader","slug":"invalid-scientific-notation-exponent-exponent","errorCode":null,"errorMessage":"Invalid scientific notation exponent '{exponent}': must be a valid number","messagePattern":"Invalid scientific notation exponent '(.+?)': must be a valid number","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/core/src/ffi/parsing.rs","lineNumber":214,"sourceCode":"        let exponent = value\n            .split(\"e-\")\n            .nth(1)\n            .expect(\"Invalid scientific notation format: missing exponent after 'e-'\");\n\n        if let Ok(exponent) = exponent.parse::<u64>() {\n            return u8::try_from(exponent).unwrap_or(u8::MAX);\n        }\n\n        assert!(\n            !exponent.is_empty(),\n            \"Invalid scientific notation format: missing exponent after 'e-'\"\n        );\n\n        if exponent.chars().all(|c| c.is_ascii_digit()) {\n            return u8::MAX;\n        }\n\n        panic!(\"Invalid scientific notation exponent '{exponent}': must be a valid number\");\n    }\n\n    value.split_once('.').map_or(0, |(_, decimal)| {\n        u8::try_from(decimal.len()).unwrap_or(u8::MAX)\n    })\n}\n\n/// Return a `bool` value from the given `u8`.\n#[must_use]\npub const fn u8_as_bool(value: u8) -> bool {\n    value != 0\n}\n\n#[cfg(test)]\nmod tests {\n    use std::ffi::CString;\n\n    use rstest::rstest;","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/core/src/ffi/parsing.rs#L196-L232","documentation":"`precision_from_v1_str` is a temporary legacy parser that infers a price-precision u8 from v1 source text. When the text uses scientific notation (`e-`), the exponent substring must parse as a number; if it contains non-digit characters the parser panics with this message. It protects against silently mapping malformed exponents (like `1e-abc`) to a wrong precision. The panic is converted to an abort via `abort_on_panic` in the FFI entry `precision_from_cstr`.","triggerScenarios":"Passing a C string price/increment like `\"1.5e-XY\"` or `\"2e-1_0\"` (exponent with non-digit chars) to `precision_from_cstr`; only strings containing `e-` with a garbage exponent reach the panic — plain decimals or missing/empty exponents panic earlier with different messages.","commonSituations":"Feeding locale-mangled numbers (thousands separators, letters) from legacy v1 config or CSV data into the FFI boundary; hand-written adapter configs where an increment was typed incorrectly; truncation of a number string mid-exponent.","solutions":["Correct the source string so the exponent is all digits (e.g. `1.5e-6`).","Sanitize/validate numeric strings on the caller side before crossing the FFI boundary.","Convert the value to plain decimal notation instead of scientific notation when generating v1 data.","Pre-validate with an equivalent parser, since `abort_on_panic` turns the panic into a process abort."],"exampleFix":"// before (data)\nprice_increment = \"1e-5x\"\n// after\nprice_increment = \"1e-5\"  // or \"0.00001\"","handlingStrategy":"validation","validationCode":"// Validate before crossing the FFI boundary\nfn is_v1_numeric(s: &str) -> bool {\n    let s = s.trim().to_ascii_lowercase();\n    if let Some(idx) = s.find(\"e-\") {\n        let exp = &s[idx + 2..];\n        return !exp.is_empty() && exp.chars().all(|c| c.is_ascii_digit());\n    }\n    true\n}","typeGuard":"fn valid_exponent(s: &str) -> bool {\n    s.split_once(\"e-\").map_or(true, |(_, exp)| !exp.is_empty() && exp.chars().all(|c| c.is_ascii_digit()))\n}","tryCatchPattern":null,"preventionTips":["Sanitize numeric strings from legacy v1 data before FFI calls","Normalize scientific notation to plain decimals at the data source","Remember `abort_on_panic` turns this panic into a process abort — validate early","Reject strings with locale artifacts (commas, units) before parsing"],"tags":["rust","ffi","parsing","scientific-notation","panic"],"backgroundTag":"invalid-argument-format","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}