Skip to content

Inside a number

There is one number type in mlog — double precision. Hence both the 53-bit limit and 0.1 + 0.2 ≠ 0.3

There is no separate integer type in mlog. Every number is a double-precision float, the same double as in most languages: 64 bits, of which 52 hold the mantissa, 11 the exponent and one the sign.

Everything that follows comes out of that arrangement: why integers are exact up to a certain limit, why 0.1 + 0.2 is not 0.3, and which bits survive packing.

tick 0
Operation0
=
Operation1
=
Operation2
=
Operation3
=
Operation4
=
Operation5
=
Operation6
=
Stop7
@counter0number
withOnenullnull
lostBitnullnull
checknullnull
fractionnullnull
failurenullnull

The mantissa stores 52 bits plus one implied — 53 significant bits in all. While a number fits in them it is an exact integer; past that a gap opens up between neighbouring representable numbers.

Number What happens to it
up to 2⁵³ − 1 stored exactly, bit for bit
2⁵³ = 9,007,199,254,740,992 the last integer before the gaps start
2⁵³ + 1 does not exist: rounds back to 2⁵³
beyond the step grows: first every other one, then every fourth, eighth…

The example shows it directly: withOne is 2⁵² with a one mixed in, and the one is there. lostBit is 2⁵³ with the same one, and it is not: check is zero, so the number stayed exactly 2⁵³.

For bitwise operations the rule is simple:

  • bits 0–52 are reliable. You can set them, read them, shift them — the number survives;
  • bits 53–63 exist inside the operation but not in the variable. The game does bitwise operations on a 64-bit integer and puts the result back into a float — and the excess is rounded away. What gets lost are the low bits: the high ones stay in the number, and precision runs out from the bottom.
op shl tag 1 52
op or packed packed tag

That is fine. But op shl tag 1 53 is not: the value will come back into the variable, only the low bits will not be able to live next to it.

A fractional number is stored in the same 53 bits, only now it is not “how many units” but “how many binary fractions”. Decimal 0.1 in binary goes on forever — like 1/3 in decimal — so the nearest representable value is what gets stored.

In the example fraction is 0.30000000000000004, and failure — the difference from a real 0.3 — is about 5.55 · 10⁻¹⁷.

Division by zero gives infinity, the root of a negative number gives NaN. A variable stores neither: on assignment such a value turns into null.

op div bad 1 0

bad will hold null, not “infinity”. So a division-by-zero check usually looks like a null check after the operation — or a check of the divisor before it.

Unofficial fan project, not affiliated with Anuke. Mindustry sprites, fonts and translations © Anuke, used under GPL-3.0; Fira Code under OFL-1.1. Site code is GPL-3.0.