{"record":{"id":"56e84e8006beb8cd","repo":"Humanizr/Humanizer","slug":"scale-leading-compound-parser-profiles-require-des","errorCode":null,"errorMessage":"Scale-leading compound parser profiles require descending scales where each larger scale is divisible by the next smaller scale.","messagePattern":"Scale-leading compound parser profiles require descending scales where each larger scale is divisible by the next smaller scale\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Localisation/WordsToNumber/ScaleLeadingCompoundWordsToNumberConverter.cs","lineNumber":298,"sourceCode":"            return true;\n        }\n\n        parsedValue = default;\n        return false;\n    }\n\n    ulong GetMaximumCountForScale(int scaleIndex)\n    {\n        if (scaleIndex == 0)\n        {\n            return ulong.MaxValue / (ulong)profile.Scales[scaleIndex].Value;\n        }\n\n        var previous = (ulong)profile.Scales[scaleIndex - 1].Value;\n        var current = (ulong)profile.Scales[scaleIndex].Value;\n        if (previous <= current || previous % current != 0)\n        {\n            throw new InvalidOperationException(\"Scale-leading compound parser profiles require descending scales where each larger scale is divisible by the next smaller scale.\");\n        }\n\n        return previous / current - 1UL;\n    }\n\n    bool TryParseOrdinalCandidate(string originalCandidate, string strippedCandidate, bool negative, out long parsedValue)\n    {\n        if (TryParseOrdinalCandidate(originalCandidate, negative, out parsedValue))\n        {\n            return true;\n        }\n\n        return !string.Equals(originalCandidate, strippedCandidate, StringComparison.Ordinal) &&\n            TryParseOrdinalCandidate(strippedCandidate, negative, out parsedValue);\n    }\n\n    bool TryParseOrdinalCandidate(string candidate, bool negative, out long parsedValue)\n    {","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Localisation/WordsToNumber/ScaleLeadingCompoundWordsToNumberConverter.cs#L280-L316","documentation":"ScaleLeadingCompoundWordsToNumberConverter.GetMaximumCountForScale throws this InvalidOperationException at line 298 when adjacent scale values are not strictly descending with the larger evenly divisible by the smaller. The scale-leading grammar multiplies counts against scales, so a non-divisible or non-descending ladder makes the math ambiguous. This is a runtime invariant check on profile data, so it indicates a misconfigured locale profile rather than bad user input.","triggerScenarios":"A ScaleLeadingCompoundWordsToNumberProfile whose Scales array is not strictly descending or not cleanly divisible (e.g. [..., 1000, 100, 60] where 100 is not divisible by 60). It fires during a parse call that reaches that scale index.","commonSituations":"Editing a scale-leading locale's scale ladder; a typo making a larger scale smaller than the next; using mixed radix (e.g. 60/24 time-like scales) that violates divisibility; generator output from malformed YAML.","solutions":["Fix the locale's scale ladder so each scale is strictly smaller than the previous and the previous is divisible by it.","Run the locale's words-to-number tests to confirm the corrected ladder parses correctly.","If you need non-divisible scales, that grammar is not supported here; use a different converter family."],"exampleFix":"// before (profile scales)\nScales: [ 1000000, 1000, 100, 60 ]  // 100 % 60 != 0 -> throws\n// after\nScales: [ 1000000, 1000, 100 ]      // 1000 % 100 == 0 -> ok","handlingStrategy":"validation","validationCode":"// Authoring guard: verify the scale ladder is strictly descending & divisible.\nstatic bool ScalesAreValid(long[] scales)\n{\n    for (int i = 0; i < scales.Length; i++)\n    {\n        if (scales[i] <= 0) return false;\n        if (i > 0 && (scales[i - 1] <= scales[i] || scales[i - 1] % scales[i] != 0))\n            return false;\n    }\n    return true;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Author scale-leading ladders as strictly descending with each larger divisible by the next (e.g. 1e6, 1e3, 1e2).","Add locale tests that parse representative values after editing scales.","Do not use mixed non-divisible radix here; pick a different converter family.","Report as a locale-data bug if encountered as an end user."],"tags":["words-to-number","scale-leading-compound","locale-authoring","config","invalidoperationexception"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}