cint

time resolution compatibility as checked integer arithmetic

Harriett Little. Published 2026-10-05.
The program in this page runs in your browser on cint_ref, the reference. Edit it and run it again.

A 2026 paper at the SISO Simulation Innovation Workshop (SIW) by Crues and Dexter of NASA Johnson Space Center sets out how the time resolutions of a simulation executive, its scheduled jobs and an HLA federation have to fit together. It passes on an observation from a 2011 SIW paper: an integer representation of executive time is preferred over a purely floating-point one. This page takes that at its word. The rules are written as a small program in cint, a C family language whose arithmetic is all integer and whose plain operators are all checked, and they come out as a few lines of modular arithmetic. The line to watch is the last one, where a one-year scenario at one picosecond does not fit a 64-bit integer and the program stops with a fault record instead of a wrong number.

the problem

A simulation executive counts time in whole ticks. Trick does, at a multiplier of ticks per second that the simulation chooses. Every job rate the executive schedules has to be a whole number of ticks, so the multiplier must be a common multiple of the job rates. An HLA federation using integer logical time counts in whole ticks too, in its own unit, which the SpaceFOM fixes at microseconds. A federate's multiplier has to be at least the federation's and a whole multiple of it, so that every federation tick lands on a federate tick. Where the federate is finer, an event it sends has to sit on a federation tick as well, or it cannot be sent at the time it happened. The paper writes these as equations in tick multipliers: Mp for the smallest compatible executive multiplier, Mf for a federate, Mhlt for the federation, and Rf for their ratio.

There is a second constraint, which TrickHLA states beside each base time unit it offers: range. A 64-bit integer holds about 9.2 x 1018 ticks. At one microsecond that is about 292,000 years. At one picosecond it is about 106 days, which is shorter than many scenarios. A multiplier chosen for resolution can silently cost the range the scenario needs.

how long a 64-bit tick count lasts at each base time unit 10⁻¹ 10¹ 10³ 10⁵ 10⁷ 10⁹ years until the count runs out, from zero one year 1 ms 1 ms: 292,271,023 years 292,271,023 years 1 µs 1 µs: 292,271 years 292,271 years 100 ns 100 ns: 29,227 years 29,227 years 10 ns 10 ns: 2,923 years 2,923 years 1 ns 1 ns: 292 years 292 years 100 ps 100 ps: 29 years 29 years 10 ps 10 ps: 2.92 years 2.92 years 1 ps 1 ps: 106.75 days 106.75 days
263 - 1 ticks, the largest I64, at each base time unit, in Julian years of 31,557,600 seconds. Only the picosecond falls short of a year. Computed from the two constants, nothing measured.
data
unitticks per secondrange
1 ms1,000292,271,023 years
1 µs1,000,000292,271 years
100 ns10,000,00029,227 years
10 ns100,000,0002,923 years
1 ns1,000,000,000292 years
100 ps10,000,000,00029 years
10 ps100,000,000,0002.92 years
1 ps1,000,000,000,000106.75 days

the checks

the program

gcd and lcm by Euclid, then the three checks as functions, then a main that runs the paper's own numbers: jobs at 128, 100, 30 and 10 Hz, a microsecond federation, federates at one microsecond, 100 nanoseconds and 1,920,000 ticks per second, a multiplier the paper uses as an example, and two events. The last three lines ask how many ticks a year is at a microsecond, at 10 nanoseconds and at a picosecond. The multiply in ticks_in is an ordinary *. In cint every * on an I64 is checked. The two test blocks at the bottom run under cint test.

time_compat.ci   Run   the first Run loads Python, about 12 MB, once

via cint_ref (reference written in .py)
stdout

    outcome
    

  

what the fault record says

The first eight lines of output are the checks passing and failing as the paper says they should: Mp = 9600, the microsecond and 100 nanosecond federates compatible with Rf of 1 and 10, the 1,920,000 federate not, one event on a tick and one between ticks with its remainder. The ninth line never prints. The record names the operation, mul.checked.i64, both operands, the exact result 31,557,600,000,000,000,000, the limit 9,223,372,036,854,775,807, and the position in the source, line 42 column 23. In C the same multiply on int64_t is undefined behavior, and on common compilers wraps to a negative tick count that the rest of the program would carry on using. Here the program stops, the eight lines it already wrote stay in stdout, and the record says exactly which quantity did not fit and by how much.

Change 1 ps to 10 ps and run again: the year fits, at about 3.2 x 1018 ticks. Make it ten years and it does not. The record follows whichever line stops fitting.

The common multiple can run out of room before the scenario does. Change the line that sets mp so that it starts at 1 and then, in a loop for hz in 1..51, becomes lcm(mp, hz), an executive with a job at every whole rate from 1 to 50 Hz, and the program stops before it prints a line. The multiplier for every rate up to 42 Hz is 219,060,189,739,591,200 ticks a second; at 43 Hz the multiply inside lcm, line 18, would make it 9,419,588,158,802,421,600, and the record carries both operands, so it says which rate broke it. Even the multiplier that fits is no use: at that many ticks a second a 64-bit count lasts 42 seconds.

the same check elsewhere

None of this needs cint, and the claim here is not that only cint can make the check. In C, GCC and Clang offer __builtin_mul_overflow, and C23 has the same thing as ckd_mul: each reports whether the product fit, and the program has to ask at every multiply and decide what to do when it did not. Rust's checked_mul returns None on overflow, while its plain *, by default, panics in a debug build and wraps in a release build. Ada's rules raise Constraint_Error unless the check has been suppressed. The claim is narrower: in cint the plain * is the checked one, in every build, so the line in ticks_in is an ordinary multiply with nothing around it, and the failure is a record, the same bytes from the reference and from the compiled C, which can be compared and hashed like any other output.

same bytes from the reference and the compiler

The compiled program and the reference write the same stdout, byte for byte, and stop at the same fault with the same record. The one emitted C file was built with four compilers on three machines, each at -O2 (/O2 for MSVC), and run once on each; every build stopped at the fault with exit status 1, the status a fault gives:

runmachinestdout SHA-256
cint_refthis page443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4
clang 18.1.3x86-64 Linux443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4
gcc 13.3.0x86-64 Linux443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4
MSVC 19.44x86-64 Windows 11443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4
Apple Clang 21.0.0arm64 macOS 27443aea258e753c7d27bcce3937834ed8b44461c7bbc3db5deb99c368c96fb3b4

The box below is the C that cintc, the cint compiler, emitted for the program as shipped. The two checked multiplies are calls to cint_mul_i64 that jump to the fault path when the result does not fit.

cintc (the cint compiler)


limits

This is one program and four rules. It is not a federate, it links no RTI, and it says nothing about how Trick or TrickHLA implement their own checks, which the paper says they are adding. The base time units named here are illustrations; which units a given middleware offers is for its documentation to say. The year is 31,557,600 seconds, a Julian year, and the picosecond case is chosen to show the range rule, not to describe any federation. The rules are the paper's; any slip in restating them is mine. Nothing here is a compliance claim and no one cited has reviewed it.

references

cite as

Harriett Little. Time resolution compatibility as checked integer arithmetic. integerc.dev, 2026. https://integerc.dev/papers/time-resolution/

Harriett Little
October 2026