OTS$ - Data Conversion Routines
Overview
VX/OTS$ is a call-compatible implementation of the OpenVMS OTS$ general-utility run-time routines for x86 and ARM Linux. It provides the radix and numeric conversions, block memory moves, string-descriptor copy, and math primitives that compiler-generated code and mixed-language OpenVMS applications call at run time, so migrated programs link and execute while keeping the same calling conventions, string descriptors, and condition-code behavior they relied on under OpenVMS.
VX/OTS$ is a library, not a server or daemon. Its routines compile into the VX runtime library and link directly into the migrated application, and each entry point is exposed under its VMS dollar-sign name alongside a generated Fortran-callable variant. Eighteen OTS$ routines are implemented and integrate with VX/RT to deliver full OpenVMS-style data processing on Linux, so applications that depend on OTS$ services can be ported without rewriting their conversion or memory-handling logic.
<!-- Webflow embed (min). Source: ots-page-technical/ots-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxotsd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture and linkage</h2><p>
VX/OTS$ is a linked run-time library, not a running service. The OTS$ entry points are
compiled from C++ into object modules, archived into the VX runtime library
<code>libvxrt.a</code>, and linked directly into the migrated application. There is no OTS$
process, no daemon, and no inter-process substrate to configure. At run time each routine
resolves a VMS string descriptor to a raw pointer and length, performs its conversion, move,
or math operation, and returns a VMS condition value.
</p><div class="table-wrapper"><table><thead><tr><th>Property</th><th>VX/OTS$</th></tr></thead><tbody><tr><td>Delivery</td><td>Object modules archived into <code>libvxrt.a</code>, linked into the application.</td></tr><tr><td>Implementation language</td><td>C++ with <code>extern "C"</code> bodies.</td></tr><tr><td>Public entry points</td><td>18 OTS$ routines, one per source module.</td></tr><tr><td>Calling model</td><td>Direct in-process function calls; no runtime server, no IPC.</td></tr><tr><td>Target platforms</td><td>x86 and ARM Linux, through the VX/RT runtime.</td></tr><tr><td>Prototypes</td><td>Shared VX headers (<code>ots$routines.h</code>, descriptor and alias helpers).</td></tr></tbody></table></div><h6>Entry points and aliasing</h6><p>
Every module binds its internal implementation symbol to the VMS-visible names through an
alias macro, so a single routine body is reachable under the upper-case VMS name, the
lower-case name, and a Fortran trailing-underscore entry point. Routines whose C and Fortran
calling conventions match share one body directly; the <code>CVT_*</code> conversions add a
separate Fortran entry point that applies a string-descriptor shim. This is what lets object
code compiled against the original OTS$ names resolve unchanged on Linux.
</p><h6>Link model</h6><style>.s7-tech-embed .process-flow {margin: 1.5rem 0;padding: 16px;border: 1px solid var(--s7t-border);border-radius: 10px;background: rgba(255, 255, 255, 0.01);}.s7-tech-embed .process-endpoint {display: grid;grid-template-columns: 72px minmax(0, 1fr);gap: 14px;align-items: center;padding: 14px 16px;border-radius: 8px;background: var(--s7t-surface);color: var(--s7t-text);}.s7-tech-embed .process-output {border: 1px solid var(--s7t-note-bd);background: var(--s7t-note-bg);}.s7-tech-embed .process-runtime {margin-top: 10px;}.s7-tech-embed .process-label,.s7-tech-embed .process-number {font-family: var(--s7t-mono);color: var(--s7t-accent);font-size: 0.76em;font-weight: 700;letter-spacing: 0.08em;text-transform: uppercase;}.s7-tech-embed .process-endpoint div,.s7-tech-embed .process-stage {min-width: 0;}.s7-tech-embed .process-endpoint strong,.s7-tech-embed .process-endpoint span:last-child,.s7-tech-embed .process-stage strong,.s7-tech-embed .process-stage span:last-child {display: block;}.s7-tech-embed .process-endpoint strong,.s7-tech-embed .process-stage strong {margin-bottom: 4px;color: var(--s7t-text);}.s7-tech-embed .process-endpoint span:last-child,.s7-tech-embed .process-stage span:last-child {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}.s7-tech-embed .process-stages {display: grid;grid-template-columns: repeat(3, minmax(0, 1fr));gap: 10px;margin: 10px 0;}.s7-tech-embed .process-stage {padding: 15px;border-top: 3px solid var(--s7t-accent);border-radius: 6px;background: var(--s7t-surface2);color: var(--s7t-text);}.s7-tech-embed .process-stage .process-number {margin-bottom: 10px;}@media (max-width: 800px) {.s7-tech-embed .process-stages {grid-template-columns: 1fr;}.s7-tech-embed .process-endpoint {grid-template-columns: 1fr;gap: 5px;}}</style><div class="process-flow" aria-label="VX/OTS link model and routine families"><div class="process-endpoint"><span class="process-label">Caller</span><div><strong>Application object code</strong><span>Calls OTS$CVT_L_TI, OTS$CVT_T_D, OTS$MOVE5, OTS$POWDD, and related routines.</span></div></div><div class="process-endpoint process-runtime"><span class="process-label">Archive</span><div><strong>OTS$ object modules in libvxrt.a</strong><span>The linker resolves each OTS$ call to its service-family implementation.</span></div></div><div class="process-stages"><div class="process-stage"><span class="process-number">01</span><strong>Radix</strong><span>Text and integer conversion across supported radices.</span></div><div class="process-stage"><span class="process-number">02</span><strong>Numeric</strong><span>Numeric parsing and formatting services.</span></div><div class="process-stage"><span class="process-number">03</span><strong>Memory</strong><span>Descriptor-aware move and fill operations.</span></div><div class="process-stage"><span class="process-number">04</span><strong>Math</strong><span>Power and related arithmetic routines.</span></div><div class="process-stage"><span class="process-number">05</span><strong>String</strong><span>String conversion and descriptor handling.</span></div></div><div class="process-endpoint process-output"><span class="process-label">Target</span><div><strong>VX/RT runtime</strong><span>x86 or ARM Linux.</span></div></div></div><div class="callout"><strong>No server to administer.</strong> OTS$ services resolve at link time and execute
in-process. Deploying VX/OTS$ means shipping the linked binary, not standing up a service or
configuring a daemon.
</div><h2 id="callable-routines">VX/OTS$ callable routines</h2><p>
The full set of 18 callable routines, spanning radix conversions between longwords and text,
numeric text to floating-point parsing, block memory moves, unsigned and double-precision
math, and a string-descriptor copy. Each is available under its VMS dollar-sign name as well
as the generated Fortran-callable alias described above.
</p><div class="table-wrapper"><table><thead><tr><th>OTS$ API</th><th>Function</th></tr></thead><tbody><tr><td><code>OTS$CNVOUT</code></td><td>Convert a double-precision float to a right-justified character string.</td></tr><tr><td><code>OTS$CVT_L_TB</code></td><td>Convert a longword to a binary (base-2) text string.</td></tr><tr><td><code>OTS$CVT_L_TI</code></td><td>Convert a longword to a signed decimal text string.</td></tr><tr><td><code>OTS$CVT_L_TO</code></td><td>Convert a longword to an octal text string.</td></tr><tr><td><code>OTS$CVT_L_TU</code></td><td>Convert a longword to an unsigned decimal text string.</td></tr><tr><td><code>OTS$CVT_L_TZ</code></td><td>Convert a longword to a hexadecimal text string.</td></tr><tr><td><code>OTS$CVT_T_D</code></td><td>Convert a numeric text string to a floating-point value (D_floating, returns a double).</td></tr><tr><td><code>OTS$CVT_T_G</code></td><td>Convert a numeric text string to a floating-point value (G_floating).</td></tr><tr><td><code>OTS$CVT_T_H</code></td><td>Convert a numeric text string to a floating-point value (H_floating).</td></tr><tr><td><code>OTS$CVT_TB_L</code></td><td>Convert a binary (base-2) text string to a longword.</td></tr><tr><td><code>OTS$CVT_TI_L</code></td><td>Convert a signed decimal text string to a longword.</td></tr><tr><td><code>OTS$CVT_TU_L</code></td><td>Convert an unsigned decimal text string to a longword.</td></tr><tr><td><code>OTS$CVT_TZ_L</code></td><td>Convert a hexadecimal text string to a longword.</td></tr><tr><td><code>OTS$MOVE3</code></td><td>Move a block of memory from source to destination (3-argument form).</td></tr><tr><td><code>OTS$MOVE5</code></td><td>Move bytes from source to destination and pad the remainder of the destination with a fill character (5-argument form).</td></tr><tr><td><code>OTS$POWDD</code></td><td>Raise a double-precision base to a double-precision power.</td></tr><tr><td><code>OTS$POWLULU</code></td><td>Raise an unsigned longword to an unsigned longword power (64-bit unsigned result).</td></tr><tr><td><code>OTS$SCOPY_R_DX</code></td><td>Copy a source string passed by reference to a destination string passed by descriptor.</td></tr></tbody></table></div><h6>Naming convention</h6><p>
In <code>CVT_L_Tx</code>, the <code>x</code> is the output radix or format of the text produced
from a longword; in <code>CVT_Tx_L</code>, the <code>x</code> is the input radix or format
parsed from text back into a longword. <code>B</code> is binary, <code>I</code> is signed
decimal, <code>O</code> is octal, <code>U</code> is unsigned decimal, and <code>Z</code> is
hexadecimal. For <code>CVT_T_x</code>, the <code>x</code> is the target floating-point kind
(<code>D</code>, <code>G</code>, or <code>H</code>).
</p><div class="table-wrapper"><table><thead><tr><th>Suffix</th><th>Meaning</th><th>Longword to text</th><th>Text to longword</th></tr></thead><tbody><tr><td><code>B</code></td><td>binary (base-2)</td><td><code>OTS$CVT_L_TB</code></td><td><code>OTS$CVT_TB_L</code></td></tr><tr><td><code>I</code></td><td>signed decimal</td><td><code>OTS$CVT_L_TI</code></td><td><code>OTS$CVT_TI_L</code></td></tr><tr><td><code>O</code></td><td>octal</td><td><code>OTS$CVT_L_TO</code></td><td>no inverse</td></tr><tr><td><code>U</code></td><td>unsigned decimal</td><td><code>OTS$CVT_L_TU</code></td><td><code>OTS$CVT_TU_L</code></td></tr><tr><td><code>Z</code></td><td>hexadecimal</td><td><code>OTS$CVT_L_TZ</code></td><td><code>OTS$CVT_TZ_L</code></td></tr></tbody></table></div><h6>Call pattern</h6><p>
Every longword-to-text conversion follows the same shape: resolve the VMS string descriptor
to a pointer and length, blank-fill the destination, format the value, right-justify the
result, and return a condition value. On overflow the destination is filled with asterisks and
an output-conversion error is returned. <code>OTS$CVT_L_TI</code> (longword to signed-decimal
text) is representative:
</p>
<pre><span class="cmt">/* OTS$CVT_L_TI: longword to signed-decimal text */</span>
U32 OTS_D_CVT_L_TI( void *value, void *string, S32 digits, S32 size, S32 flags )
{
char *sPtr = vxrt_strptr64( string, lenptr ); <span class="cmt">/* resolve VMS descriptor */</span>
memset( sPtr, ' ', sLen ); <span class="cmt">/* blank-fill destination */</span>
switch (size) { <span class="cmt">/* endian-safe longword read */</span>
case 1: sprintf( str, <span class="str">"%d"</span>, *((char *)value) ); break;
case 2: sprintf( str, <span class="str">"%d"</span>, Mgw(value) ); break;
case 0:
case 4: sprintf( str, <span class="str">"%d"</span>, Mgi(value) ); break;
}
if ( dest_too_small ) { <span class="cmt">/* fill with '*', return error */</span>
memset( sPtr, '*', sLen );
return OTS_s_OUTCONERR;
}
<span class="cmt">/* right-justify the text into the descriptor, then return success */</span>
return SS_s_NORMAL;
}</pre>
<p>
The other <code>CVT_L_T*</code> routines are structurally identical: only the format string
(<code>"%o"</code>, <code>"%u"</code>, <code>"%X"</code>, or the binary bit-shift loop) changes.
</p><h2 id="status-codes">Status codes and flags</h2><p>
OTS$ routines return a VMS condition value. Success has the low bit set, so callers that test
the low bit continue to see the same pass or fail result they saw under OpenVMS. The
conversion routines report input and output conversion errors with the standard OTS$ condition
codes, while the memory and parameter checks use the shared SS$ status set.
</p><div class="table-wrapper"><table><thead><tr><th>Symbol</th><th>Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>SS$_NORMAL</code></td><td>1</td><td>Success. Returned by every routine on the normal path.</td></tr><tr><td><code>SS$_BADPARAM</code></td><td>20</td><td>Bad parameter, for example a null destination or an invalid size.</td></tr><tr><td><code>SS$_INVARG</code></td><td>4042</td><td>Invalid argument, for example a move length above the 2147483647 limit.</td></tr><tr><td><code>OTS$_INPCONERR</code></td><td>1540140</td><td>Input-conversion error, returned by the text-to-value routines.</td></tr><tr><td><code>OTS$_OUTCONERR</code></td><td>1540148</td><td>Output-conversion error, returned when a formatted result does not fit the destination.</td></tr></tbody></table></div><h6>Blank and tab handling (text to longword)</h6><p>
The text-to-longword conversions accept a flag that controls how leading blanks and tabs in
the input are treated. <code>OTS$CVT_TB_L</code> uses the following values:
</p><div class="table-wrapper"><table><thead><tr><th>Flag</th><th>Value</th><th>Behavior</th></tr></thead><tbody><tr><td>Handle blanks and tabs</td><td><code>0x00</code></td><td>Process both blanks and tabs.</td></tr><tr><td>Ignore blanks, handle tabs</td><td><code>0x01</code></td><td>Skip blanks, process tabs.</td></tr><tr><td>Ignore tabs, handle blanks</td><td><code>0x10</code></td><td>Process blanks, skip tabs.</td></tr><tr><td>Ignore both</td><td><code>0x11</code></td><td>Skip both blanks and tabs.</td></tr></tbody></table></div><h6>Text-to-floating flag bits</h6><p><code>OTS$CVT_T_D</code> is the workhorse behind the text-to-floating conversions and accepts a
flags word. The bits VX/OTS$ implements, and those it does not, are:
</p><div class="table-wrapper"><table><thead><tr><th>Flag</th><th>Mask</th><th>Effect</th><th>Status</th></tr></thead><tbody><tr><td>Blank handling</td><td><code>0x0001</code></td><td>Replace or ignore embedded blanks.</td><td><span class="ac-live">implemented</span></td></tr><tr><td>Tab handling</td><td><code>0x0010</code></td><td>Reject strings that contain a tab.</td><td><span class="ac-live">implemented</span></td></tr><tr><td>Valid exponent</td><td><code>0x0020</code></td><td>Reject a bare sign with no exponent digits.</td><td><span class="ac-live">implemented</span></td></tr><tr><td>Scale</td><td><code>0x0040</code></td><td>Apply a decimal scale shift to the result.</td><td><span class="ac-live">implemented</span></td></tr><tr><td>Exponent only</td><td><code>0x0002</code></td><td>Exponent-only parsing.</td><td><span class="ac-no">not supported</span></td></tr><tr><td>Ignore underflow</td><td><code>0x0004</code></td><td>Ignore underflow instead of returning an error.</td><td><span class="ac-no">not supported</span></td></tr><tr><td>Truncate</td><td><code>0x0008</code></td><td>Truncation or rounding control.</td><td><span class="ac-no">not supported</span></td></tr></tbody></table></div><h2 id="compatibility">Compatibility and divergences</h2><p>
VX/OTS$ is built so OpenVMS applications that call OTS$ routines run on Linux without source
changes. A few behaviors differ from the original VMS runtime by design, because the target
platform differs. Each divergence keeps application-visible results intact while adapting to
Linux.
</p><div class="table-wrapper"><table><thead><tr><th>Area</th><th>Behavior on VX/OTS$</th></tr></thead><tbody><tr><td><code>OTS$CVT_L_TZ</code> / <code>OTS$CVT_TZ_L</code></td><td>The <code>Z</code> suffix denotes hexadecimal, matching the OpenVMS meaning; these routines convert between longwords and hexadecimal text. Older documentation that labels them as timezone conversions is incorrect.</td></tr><tr><td><code>OTS$CVT_T_G</code> / <code>OTS$CVT_T_H</code></td><td>G_floating and H_floating parsing share the D_floating path. Linux has no native VAX G or H floating type, so all three resolve to IEEE double precision.</td></tr><tr><td>Octal inverse</td><td>Octal has a forward converter (<code>OTS$CVT_L_TO</code>) but no text-to-longword inverse. The binary, signed-decimal, unsigned-decimal, and hexadecimal radixes all provide both directions.</td></tr><tr><td><code>OTS$CVT_T_D</code> extensions</td><td>Exponent-only parsing, ignore-underflow, and truncation control are not implemented, and both underflow and overflow always return an error. Standard numeric text conversion is fully supported.</td></tr><tr><td><code>OTS$POWLULU</code></td><td>Unsigned-longword exponentiation uses an iterative multiply and produces the correct result without overflow checking.</td></tr><tr><td><code>OTS$MOVE5</code></td><td>Returns a zero value on a bad or zero-length destination rather than a named condition symbol; the low-bit test still reports failure, and success returns the normal status.</td></tr></tbody></table></div><div class="callout warn"><strong>Routine names can be misleading.</strong> Several OTS$ names describe a format the
routine does not produce; the behavior documented here comes from the routine itself, not its
name. The most common example is the <code>Z</code> suffix, which is hexadecimal rather than a
timezone conversion.
</div><div class="callout"><strong>Ships inside VX/RT.</strong> OTS$ is delivered as part of the VX/RT run-time, so a
migrated application picks up OTS$ conversion, memory, and math services alongside the rest of
the OpenVMS run-time surface on x86 and ARM Linux.
</div><h2 id="quickref">Quick reference</h2><h6>Longword to text (radix conversions)</h6><div class="kw-grid"><div>OTS$CVT_L_TB</div><div>OTS$CVT_L_TI</div><div>OTS$CVT_L_TO</div><div>OTS$CVT_L_TU</div><div>OTS$CVT_L_TZ</div><div>OTS$CNVOUT</div></div><h6>Text to longword (inverse conversions)</h6><div class="kw-grid"><div>OTS$CVT_TB_L</div><div>OTS$CVT_TI_L</div><div>OTS$CVT_TU_L</div><div>OTS$CVT_TZ_L</div></div><h6>Text to floating point</h6><div class="kw-grid"><div>OTS$CVT_T_D</div><div>OTS$CVT_T_G</div><div>OTS$CVT_T_H</div></div><h6>Memory, math, and string</h6><div class="kw-grid"><div>OTS$MOVE3</div><div>OTS$MOVE5</div><div>OTS$POWDD</div><div>OTS$POWLULU</div><div>OTS$SCOPY_R_DX</div></div><h6>Key facts</h6><div class="table-wrapper"><table><thead><tr><th>Item</th><th>Value</th></tr></thead><tbody><tr><td>Public entry points</td><td>18 OTS$ routines</td></tr><tr><td>Delivered in</td><td>VX/RT run-time, archived into <code>libvxrt.a</code></td></tr><tr><td>Target platforms</td><td>x86 and ARM Linux</td></tr><tr><td>Success test</td><td>Low bit set (<code>SS$_NORMAL</code> is 1)</td></tr><tr><td>Common errors</td><td><code>OTS$_INPCONERR</code>, <code>OTS$_OUTCONERR</code>, <code>SS$_BADPARAM</code>, <code>SS$_INVARG</code></td></tr></tbody></table></div></div>