Two simulations that propagate the same orbit from the same state with the same integrator should arrive at the same place, and in floating point they do not, quite. The arithmetic is specified to the bit, but what a compiler makes of an expression is not: one fuses a multiply and an add into a single rounding and another does not, one library's square root differs from another's in the last place, and after enough steps the two trajectories are different numbers. In a distributed simulation, where federates on different machines may each carry a copy of a state, that is a disagreement nobody chose. This page propagates an orbit in checked integer arithmetic, where there is only one answer, and shows the same bytes from four compilers on three machines after a hundred million steps, beside the same integrator in double from the same compilers, which do not agree. It also shows what that costs: the integer program is twenty-five to forty-four times slower than the double one.
They are easy to run together and this page keeps them apart. Truncation error is the integrator's: a fixed step of one second with a first-order method puts the vessel a few kilometers from where the exact solution would, and it does so identically on every machine, because it is a property of the method and not of the hardware. Reproducibility is the other thing: whether two machines running the same method get the same bits. Floating point fails the second even when both machines pass the first by the same margin. Integer arithmetic has only the first. The orbit below is not more accurate than its floating-point twin, and the page does not say it is. It is the same everywhere.
Earth, a circular orbit at 6,778.137 km, one second per step, semi-implicit Euler: kick the velocity by gravity, then drift the position by the new velocity. Position is in centimeters, velocity in micrometers per second, and the part of a step that is less than a centimeter is carried in a remainder of its own, so nothing is dropped and the position is exact to the micrometer. Gravity is GM / r², with the square root an integer square root and the two divisions a muldiv each, which keeps the product wide and rounds once, by name. The radius squared in centimeters is about 4.6 x 1017, inside I64 with room to spare, and that is the whole reason for the units: they were chosen so that every intermediate fits, and if one did not the program would stop and say which. A digest of every state is folded along the way; it wraps on purpose, marked with the wrapping operators, because it is a fingerprint and not a quantity.
orbit.ci Run the first Run loads Python, about 12 MB, once
One period, 5,554 steps. The vessel comes back to within 2.85 kilometers of its start, which is the truncation error of a one-second first-order step and the fact that 5,554 seconds is not quite the period, and the radius stays within 3.9 kilometers of its starting value through the whole orbit, the small oscillation this integrator is known for. Then the digest. Run it twice and the digest is the same. Run it in the compiler and the digest is the same. Change the step or the starting speed and it changes, as it should.
cint_ref with one print line added. It swings between 6,774.307 and 6,781.975 km once per orbit, the oscillation of the semi-implicit step. The shaded band is the range over all 10⁸ steps of the compiled run, 6,774.295 to 6,781.985 km: after about 18,000 orbits it reaches 11.6 m lower and 9.7 m higher than the first orbit did.| second | radius, km |
|---|---|
| 0 | 6,778.137 |
| 100 | 6,777.704 |
| 200 | 6,777.277 |
| 300 | 6,776.861 |
| 400 | 6,776.461 |
| 500 | 6,776.082 |
| 600 | 6,775.730 |
| 700 | 6,775.409 |
| 800 | 6,775.122 |
| 900 | 6,774.875 |
| 1,000 | 6,774.669 |
| 1,100 | 6,774.507 |
| 1,200 | 6,774.392 |
| 1,300 | 6,774.325 |
| 1,400 | 6,774.307 |
| 1,500 | 6,774.338 |
| 1,600 | 6,774.418 |
| 1,700 | 6,774.545 |
| 1,800 | 6,774.718 |
| 1,900 | 6,774.935 |
| 2,000 | 6,775.193 |
| 2,100 | 6,775.489 |
| 2,200 | 6,775.819 |
| 2,300 | 6,776.178 |
| 2,400 | 6,776.563 |
| 2,500 | 6,776.968 |
| 2,600 | 6,777.387 |
| 2,700 | 6,777.817 |
| 2,800 | 6,778.250 |
| 2,900 | 6,778.682 |
| 3,000 | 6,779.107 |
| 3,100 | 6,779.520 |
| 3,200 | 6,779.915 |
| 3,300 | 6,780.287 |
| 3,400 | 6,780.632 |
| 3,500 | 6,780.945 |
| 3,600 | 6,781.222 |
| 3,700 | 6,781.460 |
| 3,800 | 6,781.655 |
| 3,900 | 6,781.806 |
| 4,000 | 6,781.910 |
| 4,100 | 6,781.965 |
| 4,200 | 6,781.972 |
| 4,300 | 6,781.930 |
| 4,400 | 6,781.839 |
| 4,500 | 6,781.702 |
| 4,600 | 6,781.518 |
| 4,700 | 6,781.292 |
| 4,800 | 6,781.025 |
| 4,900 | 6,780.722 |
| 5,000 | 6,780.385 |
| 5,100 | 6,780.020 |
| 5,200 | 6,779.631 |
| 5,300 | 6,779.222 |
| 5,400 | 6,778.800 |
| 5,500 | 6,778.370 |
The same program with the step count raised to 100,000,000, about 18,000 orbits, emitted to C by cintc once and built with four compilers on three machines, an x86-64 Linux host, an x86-64 Windows host and an arm64 Mac, each at -O2 (/O2 for MSVC):
| build | machine | final position, cm | state digest |
|---|---|---|---|
| clang 18.1.3 | x86-64 Linux | 24,708,944 676,979,337 | 15231276719831660197 |
| gcc 13.3.0 | x86-64 Linux | 24,708,944 676,979,337 | 15231276719831660197 |
| MSVC 19.44 | x86-64 Windows 11 | 24,708,944 676,979,337 | 15231276719831660197 |
| Apple Clang 21.0.0 | arm64 macOS 27 | 24,708,944 676,979,337 | 15231276719831660197 |
Every byte of output is identical between the four builds, the four lines and their SHA-256 alike, 68d2475161b0946c69e0373f6dbd50768ac17848e30c848ed6aa16df96608a7a. The radius stayed between 6,774.295 and 6,781.985 kilometers for the whole three years of simulated time. How long each build took, beside the same loop in double, is in what the checking costs, below.
Thirty lines of C, the same state, the same step, the same kick and drift, in meters and meters per second, with the same digest folded over the bit patterns of the state. Built four ways on the same machine, the same hundred million steps:
| build | fused multiply-add | time | final x, m | state digest |
|---|---|---|---|---|
| clang -O2 -ffp-contract=off | off | 1.5 s | 228619.66286548352 | 7140356435394844241 |
| clang -O2 -ffp-contract=fast -mfma | allowed | 1.6 s | 228619.66286548352 | 7140356435394844241 |
| gcc -O2 -ffp-contract=off | off | 1.5 s | 228619.66286548352 | 7140356435394844241 |
| gcc -O2 -mfma | gcc's default | 1.4 s | 228618.73339447728 | 295821013259388869 |
Three of the four agree and the fourth does not. gcc, at its default contraction setting with fused multiply-add available, puts the vessel 93 centimeters from where the other three put it, after the same hundred million steps of the same source on the same processor, and its digest shares nothing with theirs. Nothing is wrong with any of the four. Each is a correct compilation of a program whose result the language leaves to the compiler. That is the disagreement the integer version does not have.
orbit_double_checkpoints.c, built the four ways of the table.| step | distance, m |
|---|---|
| 3,981 | 6.98e-10 |
| 4,467 | 1.4e-09 |
| 5,012 | 1.32e-09 |
| 5,623 | 3.26e-09 |
| 6,310 | 4.66e-09 |
| 7,079 | 4.66e-09 |
| 7,943 | 7.09e-09 |
| 8,913 | 2.06e-08 |
| 10,000 | 4.68e-08 |
| 11,220 | 5.76e-08 |
| 12,589 | 9.05e-08 |
| 14,125 | 2.94e-07 |
| 15,849 | 6.1e-07 |
| 17,783 | 9.46e-07 |
| 19,953 | 1.52e-06 |
| 22,387 | 2.29e-06 |
| 25,119 | 3.22e-06 |
| 28,184 | 3.97e-06 |
| 31,623 | 4.31e-06 |
| 35,481 | 5.5e-06 |
| 39,811 | 6.61e-06 |
| 44,668 | 7.88e-06 |
| 50,119 | 9.7e-06 |
| 56,234 | 1.25e-05 |
| 63,096 | 1.54e-05 |
| 70,795 | 1.81e-05 |
| 79,433 | 2.39e-05 |
| 89,125 | 2.97e-05 |
| 100,000 | 3.58e-05 |
| 112,202 | 4.87e-05 |
| 125,893 | 6.5e-05 |
| 141,254 | 8.66e-05 |
| 158,489 | 0.000112 |
| 177,828 | 0.000142 |
| 199,526 | 0.000176 |
| 223,872 | 0.000219 |
| 251,189 | 0.000273 |
| 281,838 | 0.000336 |
| 316,228 | 0.000411 |
| 354,813 | 0.000505 |
| 398,107 | 0.00061 |
| 446,684 | 0.000723 |
| 501,187 | 0.000828 |
| 562,341 | 0.000887 |
| 630,957 | 0.000912 |
| 707,946 | 0.000897 |
| 794,328 | 0.000795 |
| 891,251 | 0.000668 |
| 1,000,000 | 0.00046 |
| 1,122,018 | 0.000219 |
| 1,258,925 | 4.91e-05 |
| 1,412,538 | 0.000374 |
| 1,584,893 | 0.000802 |
| 1,778,279 | 0.00123 |
| 1,995,262 | 0.00168 |
| 2,238,721 | 0.00195 |
| 2,511,886 | 0.00222 |
| 2,818,383 | 0.00294 |
| 3,162,278 | 0.00405 |
| 3,548,134 | 0.00552 |
| 3,981,072 | 0.00694 |
| 4,466,836 | 0.00793 |
| 5,011,872 | 0.00815 |
| 5,623,413 | 0.00861 |
| 6,309,573 | 0.00829 |
| 7,079,458 | 0.00712 |
| 7,943,282 | 0.00613 |
| 8,912,509 | 0.00703 |
| 10,000,000 | 0.0119 |
| 11,220,185 | 0.0158 |
| 12,589,254 | 0.0227 |
| 14,125,375 | 0.0346 |
| 15,848,932 | 0.047 |
| 17,782,794 | 0.0585 |
| 19,952,623 | 0.0714 |
| 22,387,211 | 0.0878 |
| 25,118,864 | 0.0941 |
| 28,183,829 | 0.106 |
| 31,622,777 | 0.13 |
| 35,481,339 | 0.165 |
| 39,810,717 | 0.221 |
| 44,668,359 | 0.272 |
| 50,118,723 | 0.305 |
| 56,234,133 | 0.33 |
| 63,095,734 | 0.393 |
| 70,794,578 | 0.509 |
| 79,432,823 | 0.631 |
| 89,125,094 | 0.792 |
| 100,000,000 | 0.93 |
The same thirty lines on the other two machines, and clang on the first once more with its vectorizer off:
| build | machine | final x, m | state digest |
|---|---|---|---|
| Apple Clang -O2 -ffp-contract=off | arm64 macOS | 228619.66286548352 | 7140356435394844241 |
| Apple Clang -O2 | arm64 macOS | 228618.73339447728 | 295821013259388869 |
| Apple Clang -O2 -ffp-contract=fast | arm64 macOS | 228618.70278263546 | 7708669496574730328 |
| MSVC /O2 /fp:precise | x86-64 Windows | 228619.66286548352 | 7140356435394844241 |
| MSVC /O2 /fp:contract | x86-64 Windows | 228619.66286548352 | 7140356435394844241 |
| MSVC /O2 /fp:fast | x86-64 Windows | 228619.57162821386 | 2618997089874377949 |
| clang -O2 -ffp-contract=fast -mfma -fno-slp-vectorize | x86-64 Linux | 228618.70278263546 | 7708669496574730328 |
On arm64 the fused multiply-add is always there, and Apple Clang's default build uses it: it lands on gcc's answer from the table above, to the bit. Allowed to fuse wherever it likes, it gives a third answer, and MSVC's fast mode a fourth. The clang build above that was allowed to fuse and still agreed with the unfused builds did so because clang packed x and y into one vector register and fused nothing there; with the vectorizer off it fuses, and gives the third answer too. Four answers from one source, every one a correct compilation, and the integer program gave one answer on all four compilers.
The hundred-million-step program against the thirty lines of double, each built at -O2 (/O2 for MSVC) and run once, one after the other on the same machine, with the double built unfused (-ffp-contract=off, or /fp:precise for MSVC):
| build | machine | checked integer | double | integer / double |
|---|---|---|---|---|
| clang 18.1.3 | x86-64 Linux | 44.6 s | 1.8 s | 25 |
| gcc 13.3.0 | x86-64 Linux | 49.8 s | 1.8 s | 28 |
| MSVC 19.44 | x86-64 Windows 11 | 61.2 s | 1.4 s | 44 |
| Apple Clang 21.0.0 | arm64 macOS 27 | 25.7 s | 0.9 s | 29 |
The double version is twenty-five to forty-four times faster, depending on the compiler. A step of the integer loop takes an integer square root and three multiply-divides with a wide product, and every plain operator in it is checked; a step of the double loop takes one hardware square root and three divisions. How the time splits between the square root, the wide products and the checks has not been measured, so the ratio is the price of the whole program, with the compiler that emitted the C on this site, and not of checking alone. Each time is a single run, so the double times here differ by a few tenths of a second from the first machine's times in the double table above, which came from other runs. It is stated here rather than left out.
The box below is the C that cintc emitted for the one-period program as shipped, 536 lines for 50 lines of cint. The compiled program and the reference write the same four lines, with stdout SHA-256 d2b4d07efa7dda4019e55138d3da0d41c0155233af360da18aeceafa3d0c76b1, and the test passes under both.
orbit_wide.ci is the same orbit with the state in I128: position in micrometers in one number, no remainder to carry, and squares of about 4.6 x 1025 with about forty bits to spare. It is the shape the state would take in a simulator that wanted femtometer remainders and planetary distances in the same word. The reference runs it; the compiler does not accept I128 yet, so the lower box will say so when it is loaded, and the hundred-million-step result for this version waits on that.
An earlier simulator by the same author, written in a different language, carried an orbit in integers with explicit units and rounding rules and recorded bit-identical state on x86-64 and AArch64 machines across 1.5 million per-tick digests, and published its state through the SpaceFOM. That work is cited below; this page is the same idea in cint, in fifty lines, with the arithmetic checked by the language rather than by the program.
The machines are one x86-64 Linux host, one x86-64 Windows host and one arm64 Mac, so the identity shown is across four compilers and two architectures, not a survey of hardware. Nothing here exchanged state over HLA or spoke to a federation; the paper's argument about distributed simulation is that two federates computing the same thing would agree, and it is made with three machines standing in for federates. The integrator is first-order with a one-second step and is not offered as an accurate propagator. There is no drag, no oblateness and no third body.
The units were chosen for this orbit. A different radius or a different step moves the intermediates, and the first thing to do with the program is to check that they still fit, which the program itself will do by faulting if they do not. The floating-point comparison is one program on three machines with four compilers; it shows that the result depends on compilation choices, which is all it is asked to show, and the size of the difference, under a meter after three years, is not a measure of anything general. The digest is a fingerprint for comparing runs and is not a cryptographic hash.
-ffp-contract, whose default in GNU C mode is fast.Harriett Little. An orbit in whole numbers, and the same bytes after a hundred million steps. integerc.dev, 2026. https://integerc.dev/papers/orbit/
Harriett Little
October 2026