Six intrinsic functions and statements give wrong results, or refuse to compile, when you pass them a national (PIC N) operand. A national item is stored as UTF-16 big-endian (N"AB" is 00 41 00 42), and each routine below reads that storage as if it were single alphanumeric bytes. The six cases:
Each case has a different wrong result in a different routine, so each has its own section. A separate report covers MOVE, the space fill, INITIALIZE and comparison of national items.
With default warnings, cobc prints -Wunfinished warnings ("handling of USAGE NATIONAL is unfinished; implementation is likely to be changed" and "handling of national literal is unfinished; implementation is likely to be changed"), with source context. To keep this report short, the commands add -Wno-unfinished. With that option, every program except those in case 4 compiles with exit code 0. On 3.2.0 the compile prints only these two lines, which come from the Ubuntu package's C compiler flags:
<command-line>: warning: ‘_FORTIFY_SOURCE’ redefined
<command-line>: note: this is the location of the previous definition
On 3.3-dev the compile prints nothing. For 3.3-dev the commands are the same, using that build's cobc, with LD_LIBRARY_PATH pointing at its libcob.
ordn.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. ORDN.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT-A PIC N(1) VALUE N"A".
01 NAT-Z PIC N(1) VALUE N"Z".
PROCEDURE DIVISION.
IF NAT-A < NAT-Z DISPLAY "A < Z" END-IF
DISPLAY FUNCTION ORD(NAT-A)
DISPLAY FUNCTION ORD(NAT-Z)
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x ordn.cob && ./ordn. The compile exits 0 and the run exits 0. Both characters get the ordinal 1:
A < Z
000000001
000000001
On 3.3-dev r5751, the same command. The compile exits 0 with no diagnostic. The run exits 1:
A < Z
libcob: error: attempt to use non-implemented function
The program prints A < Z, then two different ordinals, the first one smaller. ISO/IEC 1989:2023:
The cause is cob_intr_ord in libcob/intrinsic.c. In 3.2.0 it returns the first byte of the field plus one for every class, and for a national field that byte is the high-order 0x00. In r5751 the COB_TYPE_NATIONAL case calls error_not_implemented() at run time, while cobc compiles the program without a diagnostic.
The COB_TYPE_NATIONAL case returns the UTF-16 code unit, read from both bytes, plus one. libcob has no national program collating sequence at run time (cobc parses COLLATING SEQUENCE FOR NATIONAL but passes it on only for file keys), and national operands are compared by their code units, so this is the ordinal in the order the comparison uses. With the patch, ordn prints A < Z, 000000066, 000000091.
nrev.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. NREV.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT PIC N(2) VALUE N"AB".
PROCEDURE DIVISION.
IF FUNCTION REVERSE(NAT) = N"BA" DISPLAY "EQUAL" ELSE DISPLAY "NOT EQUAL" END-IF
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x nrev.cob && ./nrev. The compile exits 0 and the run exits 0:
NOT EQUAL
3.3-dev r5751 gives the same output, exit 0.
The program prints EQUAL. ISO/IEC 1989:2023:
cob_intr_reverse in libcob/intrinsic.c copies srcfield->data[size - i - 1] over srcfield->size bytes, whatever the field's class. N"AB" becomes 42 00 41 00, which is not a sequence of national characters at all.
For a national field the loop moves two-byte units. With the patch, nrev prints EQUAL.
ntrim.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. NTRIM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 NAT PIC N(4) VALUE N" AB ".
PROCEDURE DIVISION.
DISPLAY FUNCTION LENGTH(FUNCTION TRIM(NAT))
IF FUNCTION TRIM(NAT) = N"AB" DISPLAY "EQUAL" ELSE DISPLAY "NOT EQUAL" END-IF
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x ntrim.cob && ./ntrim. The compile exits 0 and the run exits 0:
000000003
NOT EQUAL
3.3-dev r5751 gives the same output, exit 0.
The program prints a length of 2, then EQUAL. ISO/IEC 1989:2023:
The 2014 edition has no argument-2 and states the same result in terms of "a space character", with a national function type for a national argument.
cob_intr_trim in libcob/intrinsic.c compares single bytes with ' ' from each end. N" AB " is 00 20 00 41 00 42 00 20, so the leading scan stops at the first 0x00 and the trailing scan removes one byte, which leaves 7 bytes.
For a national field both scans step over two-byte units and compare with the national space 00 20, as cobc stores N" ". LEADING and TRAILING keep their meaning. With the patch, ntrim prints 000000002 and EQUAL.
fdm.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. FDM.
PROCEDURE DIVISION.
IF FUNCTION FORMATTED-DATE (N"YYYYMMDD", 1) = N"16010101"
DISPLAY "OK"
ELSE
DISPLAY "WRONG"
END-IF
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x fdm.cob, exit 1:
fdm.cob:4: error: FUNCTION 'FORMATTED-DATE' has invalid date/time format
2 | PROGRAM-ID. FDM.
3 | PROCEDURE DIVISION.
4 > IF FUNCTION FORMATTED-DATE (N"YYYYMMDD", 1) = N"16010101"
5 | DISPLAY "OK"
6 | ELSE
3.3-dev r5751 gives the same output, exit 1.
fdl.cob (attached) is the same program with alphanumeric literals ("YYYYMMDD", "16010101"). It compiles with exit 0, and the run prints OK with exit 0 on both versions.
sib.cob (attached) shows the same refusal for the other two functions:
IDENTIFICATION DIVISION.
PROGRAM-ID. SIB.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 D PIC N(8) VALUE N"16010101".
PROCEDURE DIVISION.
DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x sib.cob, exit 1:
sib.cob:7: error: FUNCTION 'INTEGER-OF-FORMATTED-DATE' has invalid date/time format
5 | 01 D PIC N(8) VALUE N"16010101".
6 | PROCEDURE DIVISION.
7 > DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
8 | DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
9 | STOP RUN.
sib.cob:8: error: FUNCTION 'FORMATTED-TIME' has invalid date/time format
6 | PROCEDURE DIVISION.
7 | DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
8 > DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
9 | STOP RUN.
10 | <EOF>
On 3.3-dev r5751, the same command, exit 1:
sib.cob:7: error: FUNCTION 'INTEGER-OF-FORMATTED-DATE' has invalid date/time format
5 | 01 D PIC N(8) VALUE N"16010101".
6 | PROCEDURE DIVISION.
7 > DISPLAY FUNCTION INTEGER-OF-FORMATTED-DATE (N"YYYYMMDD", D)
8 | DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
9 | STOP RUN.
sib.cob:8: error: FUNCTION 'FORMATTED-TIME' has invalid date/time format
8 > DISPLAY FUNCTION LENGTH (FUNCTION FORMATTED-TIME (N"hhmmss", 0))
9 | STOP RUN.
10 | <EOF>
fdm.cob compiles and prints OK. sib.cob compiles and prints the values 1 and 6. ISO/IEC 1989:2023:
We think the fix starts in the scanner (cobc/scanner.l, the "poor-man's conversion iso-8859 -> utf-16"). It stores N"YYYYMMDD" as UTF-16, so its first byte is NUL. valid_const_date_time_args in cobc/tree.c takes that data from try_get_constant_data and passes it to valid_format, which calls cob_valid_date_format, cob_valid_time_format or cob_valid_datetime_format. Those read a single-byte C string, so they see an empty string. The check has to convert a national literal to its characters first.
After that, the run-time functions that take a format argument also need to read a national format. In libcob/intrinsic.c these are cob_intr_formatted_date, cob_intr_formatted_time, cob_intr_formatted_datetime, cob_intr_integer_of_formatted_date, cob_intr_seconds_from_formatted_time and cob_intr_test_formatted_datetime. The first three build an alphanumeric result and must return a national one, and cobc must give the function the national category, or the comparison with N"16010101" in fdm.cob compares an alphanumeric result with a national literal.
The patch attached to this report does not cover this case: the change spans cobc and six run-time functions.
tnnat.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. TNNAT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-N PIC N(3) VALUE N"123".
01 WS-R PIC S9(4) VALUE 0.
PROCEDURE DIVISION.
COMPUTE WS-R = FUNCTION NUMVAL(WS-N)
DISPLAY "NUMVAL " WS-R
COMPUTE WS-R = FUNCTION TEST-NUMVAL(WS-N)
DISPLAY "TEST-NUMVAL " WS-R
COMPUTE WS-R = FUNCTION TEST-NUMVAL-F(WS-N)
DISPLAY "TEST-NUMVAL-F " WS-R
COMPUTE WS-R = FUNCTION TEST-NUMVAL-C(WS-N)
DISPLAY "TEST-NUMVAL-C " WS-R
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x tnnat.cob && ./tnnat. The compile exits 0 and the run exits 0:
NUMVAL +0123
TEST-NUMVAL +0001
TEST-NUMVAL-F +0001
TEST-NUMVAL-C +0001
3.3-dev r5751 gives the same output, exit 0.
NUMVAL accepts the item, but all three test functions report position 1 as invalid.
The program prints NUMVAL +0123, then +0000 from each of the three test functions. ISO/IEC 1989:2023:
123 conforms to it.cob_check_numval (used by cob_intr_test_numval and cob_intr_test_numval_c) and cob_check_numval_f (used by cob_intr_test_numval_f) in libcob/intrinsic.c read srcfield->data one byte at a time as alphanumeric characters. The first byte of a national digit is 0x00. Both functions already carry the comment "FIXME later: srcfield may be of category national...".
In place of each FIXME, a national argument is checked in a copy with one byte per national character (its value for U+0000 to U+00FF, 0x00 otherwise). A character above U+00FF is never valid in these formats, so the returned position is the same as for the national argument. A national currency string for TEST-NUMVAL-C is copied the same way. One that holds a character above U+00FF is still not matched, and the patch marks that with a FIXME. With the patch, tnnat prints:
NUMVAL +0123
TEST-NUMVAL +0000
TEST-NUMVAL-F +0000
TEST-NUMVAL-C +0000
nunstr.cob (attached):
IDENTIFICATION DIVISION.
PROGRAM-ID. NUNSTR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 SRC PIC N(5) VALUE N"AB,CD".
01 R1 PIC N(5).
01 CNT PIC 9(3) VALUE 0.
01 PTR PIC 9(3) VALUE 1.
PROCEDURE DIVISION.
UNSTRING SRC DELIMITED BY N","
INTO R1 COUNT IN CNT
WITH POINTER PTR
END-UNSTRING
DISPLAY "CNT " CNT " PTR " PTR
STOP RUN.
On 3.2.0, cobc -std=cobol2014 -fformat=free -Wno-unfinished -x nunstr.cob && ./nunstr. The compile exits 0 and the run exits 0:
CNT 004 PTR 007
3.3-dev r5751 gives the same output, exit 0.
The program prints CNT 002 PTR 004. ISO/IEC 1989:2023:
In libcob/strings.c, cob_unstring_init_intern sets st->offset from the POINTER item (cob_get_int (st->ptr) - 1), cob_unstring_into_intern advances it by byte counts and stores the byte match_size as COUNT IN, and cob_unstring_finish_intern stores st->offset + 1 into the POINTER item.
For a national sending item, the three functions convert between bytes and national character positions where the POINTER and COUNT IN items are read and stored. The delimiter scan steps by national characters, so a delimiter can no longer match at an odd byte offset. With the patch, nunstr prints CNT 002 PTR 004.
national-intrinsics-and-statements.patch (attached) is against 3.3-dev r5751 and applies with patch -p1 at the top of the source tree. It covers cases 1, 2, 3, 5 and 6. Case 4 is not covered.
libcob/intrinsic.c it changes cob_intr_ord, cob_intr_reverse, cob_intr_trim, cob_check_numval and cob_check_numval_f, with a new static helper, narrow_national.libcob/strings.c it changes the three UNSTRING functions named in case 6.run_functions.at and "UNSTRING national with COUNT IN and POINTER" in run_misc.at, and ChangeLog entries in libcob and tests.With the patch, the same commands on the patched build give, for each attached program:
=== ordn
A < Z
000000066
000000091
=== nrev
EQUAL
=== ntrim
000000002
EQUAL
=== tnnat
NUMVAL +0123
TEST-NUMVAL +0000
TEST-NUMVAL-F +0000
TEST-NUMVAL-C +0000
=== nunstr
CNT 002 PTR 004
Each compile and run exits 0. fdm.cob and sib.cob still fail to compile as shown in case 4, and fdl.cob still prints OK.
make check on 3.3-dev r5751 with the patch: 1,374 tests behaved as expected, 9 were skipped, 0 failed. Unpatched, 1,372 behaved as expected, 9 were skipped and 0 failed; the difference is the two new tests, which pass. On the unpatched build the programs of the two new tests fail: the first stops with "libcob: error: attempt to use non-implemented function" (ORD), and the second reports COUNT IN 004 and POINTER 011.
FUNCTION ORD result from cob_intr_ord, but for an alphanumeric argument under a PROGRAM COLLATING SEQUENCE, not for a national argument.Versions: GnuCOBOL 3.2.0 (Ubuntu gnucobol3 3.2-4build1) and 3.3-dev r5751 (CI snapshot 3.3-dev.138 of 2026-10-29), x86_64 Linux. All runs were repeated on 2026-10-30.
Attached: ordn.cob, nrev.cob, ntrim.cob, fdm.cob, fdl.cob, sib.cob, tnnat.cob, nunstr.cob, and national-intrinsics-and-statements.patch.
Editions: the rules quoted above are from ISO/IEC 1989:2023. The same rules are in the committee draft of the 2014 edition (CD 1.2, 2009), with the TRIM wording noted in case 3, so all eight programs are valid under -std=cobol2014.
How the fix sites were found: we read the r5751 source. For cases 1, 2, 3, 5 and 6 the patch confirms the sites named. For case 4 we did not trace them in a debugger, so they are inferred.