Bug #10075
Number format validation issues
0%
Related issues
History
#1 Updated by Vladimir Tsichevski about 1 year ago
Let’s explore this 4GL procedure example:
SESSION:SET-NUMERIC-FORMAT(",",".");
DEFINE VARIABLE d AS DECIMAL NO-UNDO.
MESSAGE STRING(d, "abc->>,>>9.99<<,<<qwe abc").
This program converts a decimal value to a string using the specified format.
In OE, the program outputs abc0.00qwe abc (the value prefixed with 6 spaces). In FWD, it terminates with an error: ** Character number 16 of format abc->>,>>9.99<<,<<qwe abc is invalid. (22). This means the comma (,) at position 16 is invalid according to FWD’s logic, as it follows at least one "right balance" character (<).
The issue involves the NumberType.processDigits(..) method.
The problems:
- This format is valid in OE and successfully formats values.
- The format passes validation in other FWD components, such as when assigned to a
FILL-INwidget variable, suggesting multiple validation points in FWD handle it differently. - Format validation is an independent function and can be and should be performed before any number is formatted with it.
#3 Updated by Vladimir Tsichevski 11 months ago
- Status changed from New to WIP
- Assignee set to Vladimir Tsichevski
Number Format Specification¶
This document describes how numbers are structured with optional text around them. The format includes an optional prefix, an optional sign ("+" or "–") before the number, the number itself (with an integer part and an optional fractional part), an optional sign or closing parenthesis after the number, and an optional suffix.
This specification is implementation-agnostic.
NOTE:
This specification covers validation of format strings only (structure and static rules).
Runtime formatting behavior (padding, rounding, comma emission/collapse, etc.) is
defined in a separate document.
Format Structure¶
A number format consists of the following components, in order:
1. Prefix (Optional)¶
Definition: Any text before the number, composed of characters excluding digits, dots, asterisks, greater-than signs, and plus/minus signs.
NOTE:
A right parenthesis (")") may appear in the prefix as a literal; it does not act
as a closing sign unless it appears after the number part.
abc,((<,,((()abc— spaces are allowed in the prefix.
2. Sign Before the Number (Optional)¶
Definition: A "+" or "–" character immediately following the prefix.
Examples:+-- None
3. Number Part¶
Definition: The core number, consisting of either:- An integer part with an optional fractional part after a dot, or
- A dot followed directly by a fractional part (no integer part).
Integer Part¶
Definition: Sequences composed ofZ, z, *, >, or 9. The allowed forms are:
- One or more
>, optionally followed by more>or commas, then optionally followed by one or moreZ,z,*or commas, and finally optionally9or commas. - One or more
Z,z, or*, optionally followed by moreZ,z,*or commas, and finally optionally9or commas. - One or more
9, optionally followed by more9or commas.
Ordering constraint: If > appears, all > must come before any Z, z, *, or 9 placeholders.
- At least one placeholder from
Z,z,*,>, or9must be present. - The integer part cannot start with a comma.
Z*9>>,9,,Z,Z
,Z9— starts with a comma.
Dot¶
Definition: A single dot ("."), separating the integer and fractional parts, or preceding the fractional part if there is no integer part.
Fractional Part (Optional)¶
Definition: If present, must start with either9 or <. The forms are:
9followed by more9or commas, then optionally<or commas.<followed by more<or commas.
9,,9,<<,,- None (e.g.,
9.— dot with no fractional part)
,9— starts with a comma.
4. Sign or Closing Parenthesis After the Number (Optional)¶
Definition: A "+", "–", or single ")" immediately after the number part.
If "(" appears in the prefix and a suffix exists, exactly one ")" must appear here.
If there is no suffix, the ")" is optional.
+-)- None
5. Suffix (Optional)¶
Definition: Any text after the number, excluding digits, commas, asterisks, greater-than signs, plus/minus signs, and opening parentheses.
If present, the suffix must contain at least one character and cannot start with a dot ("."). Dots are allowed after the first character.
xyqwex.yy.z— starts with "y" and includes a dot.99.99a,— invalid: commas are not allowed in the suffix.99.99,— valid if the comma is part of the fractional part; otherwise invalid.
Extra Rules Checked Later¶
These rules are not part of the initial structural definition but are validated after parsing.
1. Length of Less-Than Span Must Be Equal to or Less Than Greater-Than Span¶
The length of the Less-Than Span must be equal to or less than the length of the Greater-Than Span.
Less-Than Span: A maximal sequence starting with "<" and containing only "<" and comma (",") characters.
Greater-Than Span: A maximal sequence starting with ">" and containing only ">" and comma (",") characters.
Error: If the Less-Than Span is longer, error 22 is raised: "Character number [position] of format [format text] is invalid."
- Okay:
->>,>,>,,>,,9.<,<,,<,,<<<— both spans are length 12. - Okay:
abc>9.<xy— both spans are length 1. - Not okay:
abc9.<,,<xy— Greater-Than Span length 0, Less-Than Span length 4.
2. Only One Sign Allowed¶
- Only one sign specification is permitted.
- Valid forms:
- A single "+" before or after the number part.
- A single "–" before or after the number part.
- One or more "(" in the prefix, with a ")" after the number part (required if there is a suffix; optional otherwise).
- No other combinations of "+", "–", or "(" are valid.
- A ")" after the number part is required only when "(" appears in the prefix and a suffix exists.
Error: If more than one sign specification is found, error 22 is raised.
Examples:- Okay:
((abc9)— multiple "(" in prefix, one ")" after number, no suffix. - Okay:
((abc9— multiple "(" in prefix, no closing parenthesis, no suffix. - Okay:
+abc9— single "+" before number. - Okay:
abc9-— single "–" after number. - Not okay:
(abc9-xy— "(" in prefix and "–" after number. - Not okay:
+abc9-xy— "+" before and "–" after number.
3. Must Have at Least One Digit Placeholder¶
Definition: The number part must include at least one placeholder character: 9, <, *, >, z, or Z.
Check: Performed after the format is assembled.
Error: If none are found, error 148 is raised: "Numeric format [format text] provides for no digits."
- Okay:
abc9xy— contains a "9". - Not okay:
abc,,xy— no digit placeholders.
Examples¶
Formats That Fit All Rules¶
abc,9xy- Prefix:
abc, - Number:
9 - Suffix:
xy
- Prefix:
((>Z*9.<)xy- Prefix:
(( - Number:
>Z*9.< - Closing parenthesis:
) - Suffix:
xy
- Prefix:
(9- Prefix:
( - Number:
9 - No suffix, no closing parenthesis (allowed)
- Prefix:
(9)- Prefix:
( - Number:
9 - Closing parenthesis:
) - No suffix
- Prefix:
.9- Prefix: None
- Number:
.9
9.- Number:
9.— integer part "9", dot, no fractional part.
- Number:
,Z9- Prefix:
, - Number:
Z9
- Prefix:
Formats That Don’t Fit¶
9.,— fractional part starts with a comma.9..xy— suffix starts with a dot.abc.,9— dot in prefix (invalid; dots are reserved for the number part).+9-— two sign specifications.abc9.<,,<xy— Less-Than Span longer than Greater-Than Span.(abc9-xy— "(" in prefix combined with "–" after number.abc.Z— fractional part starts with invalid "Z"..abc,,9xy— dot parsed as decimal delimiter; remainder treated as suffix, but commas not allowed in suffix.
#5 Updated by Vladimir Tsichevski 11 months ago
Issue 2¶
Let’s examine this 4GL procedure example:
SESSION:NUMERIC-FORMAT = "American". DEFINE VARIABLE d AS DECIMAL NO-UNDO. d = 123456.78901. MESSAGE 1 STRING(d, "abc->>,>>9.99<<<<qwe abc"). d = ?. MESSAGE 2 STRING(d, "."). d = 0. MESSAGE 3 STRING(d, ".").
In the first MESSAGE call, the number 123456.78901 is formatted using the abc->>,>>9.99<<<<qwe abc format, which is supported by both OE and FWD. The expected output is abc123,456.79qwe abc, but FWD produces ab123,456.79we abc instead.
The second MESSAGE call converts an unknown value (represented by ?) to a string using an invalid format (lacking digit placeholders). This executes without errors in both OE and FWD, suggesting that the conversion occurs before format validity is verified for unknown values.
The third MESSAGE call converts a zero value to a string using the same invalid format. In OE, this triggers error 148, suppressing the argument display. In FWD, no error is raised, and the dot (.) is displayed.
#6 Updated by Vladimir Tsichevski 11 months ago
- Related to Bug #10442: FILL-IN for Decimal Variable: Issues with Screen Value Display added
#7 Updated by Vladimir Tsichevski 11 months ago
- File NumberFormatValidationTest.cls added
The ABLUnit test class NumberFormatValidationTest.cls has been attached.
#9 Updated by Vladimir Tsichevski 11 months ago
Greg Shah wrote:
Please commit all tests into the testcases project.
1. I'm uncertain about the appropriate location within the project to place this test class.
2. I cannot test the result in testcases because I am unable to execute ant tasks in the project (see #7143-1679) :-(
#10 Updated by Greg Shah 11 months ago
See Format String Tests.
#11 Updated by Vladimir Tsichevski 11 months ago
Greg Shah wrote:
See Format String Tests.
This page points to a single test class designed to evaluate the STRING function across all data types. My test class, however, focuses on numeric format validation and does not assess the formatting output. I conclude that format validation and formatting represent two distinct concerns, with format validation always occurring prior to applying the format.
#15 Updated by Vladimir Tsichevski 11 months ago
Greg Shah wrote:
I mean in that set of tests. It probably won't be in that same class. Over time we will probably refactor the existing ABLUnit tests to be split up into separate methods, which may mean more classes too. My point is that the test set is not complete.
I successfully executed TestString.cls, but 68 out of 96 total tests failed. It’s unclear whether FWD is responsible or if the tests were imperfect. Regardless, debugging my specific issue in this state would be challenging. Therefore, I’ve decided to place NumberFormatValidationTest.cls in the same directory as TestString.cls until all tests in this class pass successfully.
#16 Updated by Greg Shah 11 months ago
Therefore, I’ve decided to place
NumberFormatValidationTest.clsin the same directory asTestString.cls
This is what I was suggesting all along. We DO NOT need to put all tests in one class. That makes no sense.
until all tests in this class pass successfully.
No, this is permanent. I don't want all code in one test class.
#17 Updated by Vladimir Tsichevski 11 months ago
Greg Shah wrote:
Therefore, I’ve decided to place
NumberFormatValidationTest.clsin the same directory asTestString.clsThis is what I was suggesting all along. We DO NOT need to put all tests in one class. That makes no sense.
until all tests in this class pass successfully.
No, this is permanent. I don't want all code in one test class.
We are fully in consensus!
#19 Updated by Vladimir Tsichevski 6 months ago
- Related to Bug #11112: Numeric FILL-IN Format Validation – GUI Rendering Failure added
#21 Updated by Vladimir Tsichevski 6 months ago
- Blocked by Bug #10471: Incorrect Integer Formatting as Legacy Time added
#22 Updated by Vladimir Tsichevski 6 months ago
Another related issue (initially reported in #10471-12):
The following statements throw error 22 ("Character number 2 of format is invalid") in OE, at position 2:
STRING(0, "+h"). STRING(0, "+hh"). STRING(0, "+hh:"). STRING(0, "+hh:m").
However, in FWD, these statements do not throw any error.
#23 Updated by Vladimir Tsichevski 6 months ago
Updated version of #10075-3.
Number Format Specification¶
This document describes the structure of number formats used for formatting and validation of numeric values, including optional text around the number.
This specification is implementation-agnostic and focuses on format string validation (static structure and rules).
Runtime formatting behavior (padding, rounding, comma emission/collapse, etc.) is defined in a separate document.
Format Structure¶
A number format consists of the following components, in strict order:
1. Prefix (Optional)¶
Definition: Any text before the number part, composed of characters excluding digits (0–9), dots (.), asterisks (*), greater-than signs (>), plus/minus signs (+, -), and Z/z.
Note: A right parenthesis ) may appear in the prefix as a literal; it does not act as a closing sign unless it appears after the number part.
Examples:
abc,((<,,((()" abc"— spaces are allowed.
2. Sign Before the Number (Optional)¶
Definition: A single + or - character immediately following the prefix.
Examples:
+-- None
3. Number Part¶
Definition: The core numeric portion, consisting of either:- An integer part optionally followed by a dot and fractional part, or
- A dot directly followed by a fractional part (no integer part).
Integer Part¶
Definition: A sequence composed of Z, z, *, >, 9, or commas ,.
Rules:
- All
>must appear before anyZ,z,*,9, or comma. - At least one digit placeholder (
Z,z,*,>,9) must be present. - Cannot start with a comma.
Examples:
Z*9>>,9,,Z,Z
,Z9— starts with a comma.
Dot¶
Definition: A single dot . separating integer and fractional parts, or starting the fractional part if no integer part exists.
Fractional Part (Optional)¶
Must start with 9 or <.
Forms:
9followed by9or commas, optionally followed by<characters<characters only (implies optional fractional digits)
Examples:
9,,9,<<,,
4. Closing Sign / Parenthesis (Optional)¶
Only allowed when prefix contains matching opening parenthesis or when using + / - after number.
5. Suffix (Optional)¶
Any text after the number part / closing sign, excluding certain control characters in some contexts.
Validation Rules¶
1. Greater-Than / Less-Than Symmetry¶
Length of Less-Than Span must be equal to or less than length of Greater-Than Span.
Error: If violated, error 22 is raised at the offending character:
"Character number [position] of format [format text] is invalid."
Examples:
- Okay:
->>,>,>,,>,,9.<,<,,<,,<<<— both spans are length 12. - Okay:
abc>9.<xy— both spans are length 1. - Not okay:
abc9.<,,<xy— Greater-Than Span length 0, Less-Than Span length 4. Character at position 6 (<) is invalid. - Not okay:
abc>9.<,,<xy— Greater-Than Span length 1, Less-Than Span length 4. Character at position 8 (,@) is invalid.
2. Only One Sign Specification Allowed¶
Valid forms:
- Single
+before or after number. - Single
-before or after number. - One or more
(in prefix + optional)after number (required if suffix exists).
Rule: No mixing of signs (e.g. no + and -, no ( and -, etc.).
Error: Multiple signs → error 22.
Examples:
- OK:
((abc9)xy - OK:
+abc9 - OK:
abc9- - Not OK:
(abc9-xy - Not OK:
+abc9-xy
3. Must Have at Least One Digit Placeholder¶
Rule: Number part must contain at least one 9, <, *, >, z, or Z.
Error: If missing → error 148:
"Numeric format [format text] provides for no digits."
Examples:
- OK:
abc9xy - Not OK:
abc,,xy
Valid Format Examples¶
abc,9xy- Prefix:
abc, - Number:
9 - Suffix:
xy
- Prefix:
((>Z*9.<)xy- Prefix:
(( - Number:
>Z*9.< - Closing parenthesis:
) - Suffix:
xy
- Prefix:
(9- Prefix:
( - Number:
9
- Prefix:
.9- Number:
.9
- Number:
9.- Number:
9.
- Number:
Invalid Format Examples¶
9.,— fractional part starts with comma.9..xy— suffix starts with dot.abc.,9— dot in prefix.+9-— two signs.abc9.<,,<xy— Less-Than Span longer than Greater-Than Span.(abc9-xy—(+-.abc.Z— fractional part starts withZ..abc,,9xy— dot as delimiter; suffix contains commas.
#24 Updated by Vladimir Tsichevski 6 months ago
Numeric Formatting Logic Specification¶
This document describes the OE runtime formatting logic for numeric values.
The logic is currently implemented in the NumericFormat Java class as part of a standalone proof-of-concept for numeric formatting.
It is intended to replace the current FWD implementation, which has known issues.
Limits and Compatibility¶
- In OE, the
DECIMALtype is effectively limited to 10 fractional digits in most operations
(e.g.0.00000000005rounds to0.0000000001). - The current Java implementation does not enforce this limit — it assumes the input
Numberalready reflects 4GL rounding behavior. - Rounding and truncation follow Java
DecimalFormatrules withRoundingMode.HALF_UP, which is consistent with 4GL behavior.
Example of 4GL fractional digit limitation:
MESSAGE 0.00000000005. // displays 0.0000000001 MESSAGE 0.00000000005 + 0.00000000005. // displays 0.0000000002
Common Steps¶
- Parse the format string
- The input format string is validated and parsed by
NumericFormatParser. - This step ensures strict conformance to the rules defined in Number Format Specification.
- The parser produces an immutable
NumericFormatinstance. - The instance contains all parsed format data used during formatting.
- It is thread-safe and reusable across multiple formatting calls.
- The input format string is validated and parsed by
- Format the number
Formatting is performed by the method
NumericFormat.format(Number, boolean, boolean).
Input Parameters¶
value: the number to format (java.lang.Number)rightAligned: iftrue, the result is left-padded with spaces to the maximum format lengthallowSignReuse: iftrue, the sign position may be reused for non-negative values
Output¶
- A formatted string that conforms to the specification.
- Throws Numbered Exception (error 74) if the value cannot be represented within the format constraints.
Sign Reuse (Space-for-Sign Optimization)¶
This optimization is enabled only when all of the following conditions are met:
- The format contains a
+or-sign placeholder before the numeric part. - The value being formatted is non-negative.
allowSignReuseistrue.
When active:
- The sign character is omitted.
- An extra digit from the value may occupy the sign's position (especially useful with
+).
The + sign position may be reused to display an extra digit only when the value exceeds the normal digit capacity.
Note:
When editing a FILL-IN field interactively, sign reuse is disabled if the last keystroke causes the formatted value (without reuse) to fully occupy the available format length without leading spaces.
This behavior is preserved in this implementation by providing the allowSignReuse flag.
Examples:
Formatting with the format "abc->>,>>9":
- For the value 12345:
# With sign reusing:
" abc12,345". The minus (-) character is omitted, and the first digit ("1") occupies its position. # Without sign reusing:" abc1,234". The minus (-) character is omitted, but a space is added instead, and the value is truncated from the right to fit into the format length. - For the value 1234:
- With sign reusing:
" abc1,234". The minus (-) character is omitted, and a space is added instead, and the value fits into the format length. - Without sign reusing:
" abc1,234". The minus (-) character is omitted, and a space is added instead, and the value fits into the format length.
- With sign reusing:
Formatting with the format "abc+>>,>>9":
- For the value 12345:
# With sign reusing:
"abc+12,345". The plus (+) character is omitted, and the first digit ("1") occupies its position. # Without sign reusing:"abc+ 1,234". The plus (+) character is omitted, and a space is added instead, and the value is truncated from the right to fit into the format length. - For the value 1234:
- With sign reusing:
"abc+ 1,234". The plus (+) character is omitted, and a space is added instead, and the value fits into the format length. - Without sign reusing:
"abc+ 1,234". The plus (+) character is omitted, and a space is added instead, and the value fits into the format length.
- With sign reusing:
Credit/Debit Sign Placeholders (CR/DB/DR)¶
Numeric formats support three ways to display the sign of a value:
- Using
+or-placeholders, either before or after the numeric part
(e.g.->>9.99,+>>9.99,>>9.99+,>>9.99-). - Using opening and closing parentheses
(e.g.((>>9.99),(>>9.99)). - Using
CR(credit),DBorDR(debit) as exact suffixes
(e.g.>>9.99DB,+>>9.99CR,>>9.99Dr,>>9.99dB).
Recognition of CR/DB/DR suffixes¶
The CR, DB, and DR modifiers:
- Affect formatting only — they are not validated during format parsing.
- Must appear exactly as the suffix (nothing after them).
→ Recognized in
>>9.99dR
→ Ignored in>>9.99abcdR,>>9.99dRabc,>>9.99dRdR. - Are case-insensitive during recognition (
DR,Dr,dR,drare equivalent). - Are case-preserving in the output (the case you write is the case you get).
Formatting behavior with CR/DB/DR¶
- For negative values: the suffix is inserted exactly as written in the format, preserving case
(e.g.
-1with>>9.99dR→" 1.00dR"). - For positive values: the suffix is replaced by two spaces
(these spaces are not trimmed from the output).
- The three modifiers (
CR,DB,DR) are interchangeable — all indicate a negative value. - They can coexist with other sign placeholders:
Pre-numeric sign (
+/-): both are applied
(e.g.-1with->>9.99dR→" -1.00dR").Post-numeric sign (
+/-) or parentheses: the CR/DB/DR suffix is ignored
(e.g.1with>>9.99-dR→" 1.00dR";
-1with(>>9.99)CR→"(123.45)").
- CR/DB/DR cannot be used for space reuse (sign position reuse)
(unlike ordinary-or+placeholders).Example (fails):
STRING(12345.456, ">>>9.99<CR")→ Value 12345.46 cannot be displayed using >>>9.99<CR.Example (succeeds):
STRING(12345.456, "->>>9.99<")→"12345.46"(ordinary sign is reused).
Formatting Workflow¶
The formatting algorithm proceeds in the following steps:
- Pre-format raw value
- Convert the input
valueto a string using a pre-configuredDecimalFormat(dot separator, no scientific notation, up to 340 fractional digits). - Result:
rawSigned(signed string). - Set
isNegativeflag if the value is negative (starts with minus sign).
Note:
This step allows uniform processing of all kinds of JavaNumberinput values, e.g.Integer,Double,BigDecimal. - Convert the input
- Validate sign for negative values
If the value is negative and the format does not allow signs (neither opening nor closing nor extra nor CR/DB/DR), throw error 74.
- Process prefix
These characters come before the number and optional left sign specification.
When formatting, the prefix is copied to output literally.- Copy prefix characters to output.
- Replace
(with((if negative) or space (if positive).
Examples:
- 1234 formatted with the
"abc9999"format gives:"abc1234". The"a","b"and"c"here are fill characters, which form the prefix and are copied verbatim. - 1234 formatted with the
"ab(c9999)"format gives:"ab c1234 ". The"a","b"and"c"here are fill characters, which, together with the"(", form the prefix part. The"("and")"are not fill characters and form sign specification. - -1234 formatted with the
"(a(b(c9999)xy"format gives:"(a(b(c1234)xy". - -1234 formatted with the
"ab(c9999"format gives:"ab(c1234". Note: in this case we have no suffix part, so the closing parenthesis is optional, while the format"ab(c1234xy"has the suffix part"xy"and no closing parenthesis, so it is invalid.
- Process opening sign (if present)
- Append
-(negative) or space (otherwise). - Compute
reusableSignIndex(position after prefix) if: *allowSignReuseis true and * value is non-negative
Examples:
- 1234 formatted with the
"+9999"format gives:"+1234". - -1234 formatted with the
"+9999"format gives:"-1234". - 1234 formatted with the
"9999+"format gives:"1234+". - -1234 formatted with the
"9999+"format gives:"1234-". - 1234 formatted with the
"-9999"format gives:" 1234". - -1234 formatted with the
"-9999"format gives:"-1234". - 1234 formatted with the
"9999-"format gives:"1234 ". - -1234 formatted with the
"9999-"format gives:"1234-".
- Append
- Count raw integer digits
Count digits before the decimal point in
rawSignedasrawIntegerDigitsAvailable. - Handle special case: zero integer part and no integer placeholders
If
maximumIntegerDigits == 0and integer part of raw value is zero:- Set
allowedFractionalDigits = minimumFractionalDigits - Pre-format value using
minimumFractionalDigits - If rounding causes carry-over (e.g.
0.995→1.00), output '1' if sign reuse is allowed, else throw error 74.
Examples:
0.0formatted with".99"gives:".00".0.99formatted with".99"gives:".99".0.995formatted with"-.99"gives"1.00". Sign is reused here, so formatting is successful.9.995formatted with"-9.99"gives:"10.00". Sign is reused here, so formatting is successful.0.999formatted with".99"throws error 74: value rounds to1and does not fit after rounding.0.995formatted with".99"throws error 74: value rounds to1and does not fit after rounding.
- Set
- Normal case
- Check if value fits:
rawIntegerDigitsAvailable ≤ maximumIntegerDigits + (reusableSignIndex ≥ 0 ? 1 : 0)
→ throw error 74 if not - Compute
unusedIntegerDigits = maximumIntegerDigits - rawIntegerDigitsAvailable - Compute
allowedFractionalDigits = minimumFractionalDigits + min(optionalFractionalDigits, unusedIntegerDigits) - Pre-format value using
minimumFractionalDigitsandallowedFractionalDigits - If integer part exists in format:
* Extract integer part from pre-formatted string
* Format it using
formatPart()(reversed) * Append to output * Throw error 74 if formatting fails (value does not fit)
Examples:
1234formatted with"999999"gives:"001234"(padded with leading zeroes).0.1234formatted with"9.999999"gives:"0.123400"(padded with trailing zeroes).-1234formatted with"-999999"gives:"-001234"(padded with leading zeroes).-0.1234formatted with"-0.999999"gives:"-0.123400"(padded with trailing zeroes).1234cannot be formatted with"999", since the value does not fit into the format. This causes an error.0.1234formatted with"9.99"gives:"0.12"(extra fraction digits are rounded).0.1256formatted with"9.99"gives:"0.13"(extra fraction digits are rounded).
- Check if value fits:
- Process fractional part (if present in format)
- Append decimal delimiter
- If
allowedFractionalDigits > 0* Extract fractional part from pre-formatted string * Format it usingformatPart()(not reversed) * Append to output
- Process closing sign / parenthesis (if present in format)
- Replace
)with)(negative) or space otherwise - Replace
-with-(negative) or space otherwise - Replace
+with-(negative) or+otherwise
- Replace
- Append suffix (if present)
If the format has content after the fractional part (
afterFractionPart < format.length()):- If CR/DB/DR suffix is detected (
hasSignCreditDebit == true): * Append the suffix exactly as written if the value is negative * Append two spaces (" ") if the value is positive * Note: These two spaces are not trimmed from the output.
- Otherwise (normal suffix / closing sign handling):
- If the character at
afterFractionPartis a closing sign/parenthesis: * Append),-, or+for negative values; append space for positive values * SetsuffixStartto the position after the closing sign - Append the remaining format substring starting from
suffixStart
- If the character at
- If CR/DB/DR suffix is detected (
- Apply sign reuse (if applicable)
If
reusableSignIndexis valid and output exceedsmaxValueLength()(or sign is-), delete character at that index. - Apply right-alignment (if requested)
Left-pad with spaces to
maxValueLength(). - Return result
The final string is returned.
The formatPart() Helper Method¶
The private method formatPart() (called from NumericFormat.format()) is responsible for formatting one contiguous segment of digit placeholders — either the integer part or the fractional part.
It implements most of the placeholder-specific rendering rules (padding, zero suppression, star/zero replacement, comma handling) and is used twice in a typical formatting flow:
- once for the integer part (with reversed digit order and left padding)
- once for the fractional part (normal left-to-right order, right padding)
Signature:
private String formatPart(
String digits, // digit string to place (without sign/decimal)
char[] placeholders, // format characters for this part, e.g. ['9','9',',','9']
boolean reverse, // true for integer part → process from right to left
boolean padWithZeroes, // true for integer part when using '9', false for 'Z'/'*'
int optionalCount // number of optional placeholders that can be collapsed
)
Behavior and Rules¶
1. Input preparation
digits— string of digits only (no sign, no decimal point)- For integer part: usually the whole number as string, possibly with leading zeros removed
- For fractional part: digits after decimal point, without leading zeros unless value is zero
2. Direction of processing
- Integer part → processed right-to-left (reverse = true)
→ aligns digits to the right, pads/suppresses on the left - Fractional part → processed left-to-right (reverse = false)
→ aligns digits to the left, pads/suppresses on the right
3. Placeholder handling
| Char | Meaning | Integer part | Fractional part |
|---|---|---|---|
9 |
Mandatory digit | pad 0 | pad 0 |
Z / z |
Optional digit — suppress leading zero | space | (not allowed) |
* |
Optional digit — suppress leading zero | * |
(not allowed) |
> |
Extra digit slot (collapsible) | space → digit | (not allowed) |
< |
Suppress trailing zero | (not allowed) | suppress 0 & comma |
, |
Grouping separator | collapse → space if no digit | collapse → one comma + 0-padding |
4. Comma (grouping separator) behavior
- Integer part — multiple consecutive commas collapse to spaces when there are not enough digits
- Fractional part — multiple consecutive commas collapse to one comma + trailing zero padding when digits are missing
5. Padding vs Suppression
- When there are more placeholders than digits:
9→ always pad with0Z/*/>→ pad with space /*/ space (left side for integer, right side for fraction)<(fraction only) → suppress trailing0and associated commas
- When there are more digits than placeholders:
* If enough
>placeholders exist → use them to display extra digits (leftmost first) * Otherwise → value does not fit → error 74
6. Examples
Integer part examples (reverse = true):
- digits = "1234", placeholders = "9,9,9,9" → "1,2,3,4"
- digits = "123", placeholders = "9,9,9,9" → " 1,2,3"
- digits = "1", placeholders = "9,9,9,9" → " 0,0,1"
- digits = "12345", placeholders = ">,>,9,9" → "12,345"
Fractional part examples (reverse = false):
- digits = "2345", placeholders = "9,9,,,,9" → "2,3,,,,5"
- digits = "23", placeholders = "9,9,,,,9" → "2,3,0,0,0"
- digits = "2", placeholders = "9,9,,,,9" → "2,0,0,0,0"
- digits = "23456", placeholders = "9.<<<<" → "2.2346" (extra digit shown)
- digits = "23000", placeholders = "9.<<<<" → "2.23" (trailing zeros suppressed)
This helper is the core of placeholder logic and is reused symmetrically for both sides of the decimal point, with only the direction and padding character changing.
Legacy Time Formatting (Integer Values)¶
Special handling exists for formatting integer values (seconds since midnight) as time strings
when the format starts with one of the exact prefixes: hh:mm, hh:mm:ss, or +hh:mm.
Detailed rules, valid patterns, and formatting behavior (including negative values, large values,
modulo 86400, hours % 60 quirk in + prefix, trailing spaces preservation, case-insensitivity of placeholders,
AM/PM placement anywhere in suffix, etc.) are documented separately in #10471.