fix(az-AZ,tr-TR): guard scale ceiling in Turkic pair#357
Merged
Conversation
Both break past their 10^21 scale vocabulary. Uniform ceiling across all three forms (ordinal builds on the cardinal via integerToWords + suffix; currency too), plus the decimal (spelled via integerToWords(BigInt(...))). az-AZ was a good catch: its ordinal does integerToWords(n).replace(/ /g, '') + suffix, so the broken cardinal "bir " (trailing space, missing scale word) becomes "bir" -> "birinci" — well-formed but WRONG (the space-strip hid the malformation from the well-formedness probe, like es-US's silent truncation). Guarding the ordinal at the cardinal ceiling (by code analysis, not the probe's "clean" verdict) fixes it. Ceilings derived from each file's scale table (az: SCALE_WORDS.length*3; tr: (SCALES.length+2)*3, since tr indexes SCALES[scaleIndex-2]). Entry -point pattern throughout. Verified per variant (well-formed or RangeError; integer/ordinal/decimal boundaries — ordinal 10^21 now throws); full suite green (269). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds scale-ceiling guards for the Turkic language pair (az-AZ, tr-TR) so values at/above the largest supported magnitude fail fast with a uniform RangeError instead of producing malformed or silently-wrong output.
Changes:
- Derive per-language maximum supported magnitude from the language scale tables and define
MAX_CARDINAL/MAX_CARDINAL_EXPONENT. - Add entry-point checks in
toCardinal,toOrdinal, andtoCurrency(and for cardinal decimals where the fraction is spelled viaintegerToWords) to throwtooLargeError(...)when out of range.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/tr-TR.js | Introduces a derived 10²¹ ceiling and applies it at public entry points (cardinal/ordinal/currency), including the integer-spelled decimal path. |
| src/az-AZ.js | Introduces a derived 10²¹ ceiling and applies it at public entry points (cardinal/ordinal/currency), including the integer-spelled decimal path. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ninth in the fix-then-gate series. The Turkic pair (az-AZ, tr-TR) — both cap at 10²¹.
What fuzzing found (and a probe false-negative worth noting)
Both break past their 10²¹ scale vocabulary. Uniform ceiling across all three forms (ordinal builds on the cardinal via
integerToWords+ suffix; currency too) plus the decimal (spelled viaintegerToWords(BigInt(remainder))).az-AZ exposed a probe blind spot. Its ordinal does
integerToWords(n).replace(/ /g, '') + suffix. Past 10²¹ the cardinal is the broken"bir "(trailing space, missing scale word) — and the space-strip turns it into"bir"→"birinci": well-formed but wrong (it looks like "1st"). The well-formedness probe reported the az ordinal "clean through 10⁶⁶" because the malformation was stripped away — same silent-wrong class as es-US's truncation. Caught it by reading the code, not trusting the probe, and guarded the ordinal at the cardinal ceiling.Fix
Entry-point pattern. Ceilings derived from each file's scale table — az:
SCALE_WORDS.length * 3; tr:(SCALES.length + 2) * 3(tr indexesSCALES[scaleIndex - 2], with units/thousands separate, like da-DK). Decimal guarded (integer-spelled).Verification
Per variant: well-formed string or
RangeErroracross±10³⁰; integer / ordinal / decimal boundary checks all confirm10²¹−1ok /10²¹throws (the az ordinal now throws instead of returning"birinci"). Existing fixtures green, lint clean, 269 tests.🤖 Generated with Claude Code