velxio/frontend/src/simulation/spice/valueParser.ts

77 lines
2.5 KiB
TypeScript

/**
* Parse a value string with SPICE-style SI suffixes.
*
* "4.7k" → 4700
* "220" → 220
* "1Meg" → 1_000_000
* "10u" → 1e-5
* "100n" → 1e-7
* "0.1u" → 1e-7
* "22p" → 2.2e-11
* "10mH" → 0.01
* "1.5H" → 1.5
*
* Recognized suffixes (case-insensitive for most; `Meg` is special):
* G = 1e9 T = 1e12 Meg = 1e6 K = 1e3
* m = 1e-3 u = 1e-6 n = 1e-9 p = 1e-12 f = 1e-15
*
* Trailing unit letters (Ω, F, H, V, A) are tolerated and stripped.
* Returns `fallback` if parsing fails.
*/
const SUFFIX_MAP: Array<[string, number]> = [
['meg', 1e6],
['g', 1e9],
['t', 1e12],
['k', 1e3],
['m', 1e-3],
['u', 1e-6],
['µ', 1e-6],
['n', 1e-9],
['p', 1e-12],
['f', 1e-15],
];
export function parseValueWithUnits(input: unknown, fallback = NaN): number {
if (typeof input === 'number' && Number.isFinite(input)) return input;
if (typeof input !== 'string') return fallback;
let s = input.trim();
if (!s) return fallback;
// Strip trailing physical unit words (non-SI-prefix ones)
s = s.replace(/(ohms?|Ω|farads?|henrys?|henries|amps?|volts?|ω|watts?)\s*$/i, '');
// Strip trailing single-letter unit (Ω, H, V, A, W). NOT F (which is femto in SPICE).
// Only strip when preceded by a digit or SI-prefix letter, not when the whole
// input is just the unit (so we don't turn "k" into "").
s = s.replace(/([0-9a-zµ])[ΩHVAW]$/i, '$1');
// Try suffix match (longest first — "meg" before "m")
const lower = s.toLowerCase();
for (const [suffix, mult] of SUFFIX_MAP) {
if (lower.endsWith(suffix)) {
const numPart = s.slice(0, s.length - suffix.length);
const n = parseFloat(numPart);
if (Number.isFinite(n)) return n * mult;
}
}
// Plain number
const n = parseFloat(s);
return Number.isFinite(n) ? n : fallback;
}
/** Format a number back into a compact SPICE-style string. */
export function formatValueWithUnits(value: number): string {
if (!Number.isFinite(value)) return '0';
const abs = Math.abs(value);
if (abs >= 1e9) return `${(value / 1e9).toPrecision(3)}G`;
if (abs >= 1e6) return `${(value / 1e6).toPrecision(3)}Meg`;
if (abs >= 1e3) return `${(value / 1e3).toPrecision(3)}k`;
if (abs >= 1) return `${value.toPrecision(3)}`;
if (abs >= 1e-3) return `${(value * 1e3).toPrecision(3)}m`;
if (abs >= 1e-6) return `${(value * 1e6).toPrecision(3)}u`;
if (abs >= 1e-9) return `${(value * 1e9).toPrecision(3)}n`;
if (abs >= 1e-12) return `${(value * 1e12).toPrecision(3)}p`;
return `${(value * 1e15).toPrecision(3)}f`;
}