SCUA has four number kinds, and choosing between them is mostly one trade-off: range versus exactness. This page covers how each is stored and when to reach for it.
int— a signed 64-bit integer. Exact whole numbers up to about 9.2 × 10¹⁸. Scores, counts, IDs, in-game gold.float— a 64-bit IEEE-754 double. Enormous range, but binary, so most decimal fractions aren't exact. Physics, ratios, anything continuous where a tiny rounding error is fine.decimal— an exact base-10 number. No rounding error, ever. Money, tax rates, measurements.big— an inexact base-10 number with effectively unbounded magnitude (about 15 significant figures). Idle-game counters that climb past what anintorfloatcan hold.
int and float are the everyday pair;
decimal and big are specialists at opposite
ends — decimal buys exactness, big buys
reach.
#How int and float are stored
An int is a signed 64-bit integer. It
is exact for every whole number in its range — roughly −9.2 × 10¹⁸ to
9.2 × 10¹⁸ — which is plenty for scores, counts, identifiers, and
currency-as-cents. When a value needs to grow past that range (idle
games), use a big.
A float is a 64-bit IEEE-754 double —
the same floating-point type C, JavaScript, and most languages use. It
reaches up to about 1.8 × 10³⁰⁸, but it stores values in
binary, so exact-looking decimals like 0.1 have no
exact binary form and carry a tiny error. That's fine for physics and
ratios; it is not fine for money (see decimals).
#Integers and floats don't mix silently
An int and a float are different kinds, and
arithmetic keeps them apart. Adding two ints gives an int; if a float is
involved, the result is a float. There is no silent demotion from float
to int.
print(2 + 3)
print(type(2 + 3))
print(7 / 2)
print(type(7 / 2))
print(7 // 2)
print(type(7 // 2))
print(2 + 3.0)
print(type(2 + 3.0))
Run it:
$ scua numbers.scua
5
int
3.5
float
3
int
5
float
Two things to take from this:
/is true division and always gives a float, even when it divides evenly. Use//for floor division that stays an int.- Mixing an int and a float in one expression produces a float
(
2 + 3.0is5as a float). That is the only direction the conversion happens. An int never becomes a float on its own, and a float never silently rounds to an int.
There's one display quirk worth knowing. A float whose value happens
to be whole prints without a decimal point, so it looks like an int even
though type() says otherwise:
let half = 2.0
print(half)
print(type(half))
print(half == 2)
$ scua whole-float.scua
2
float
true
The value is still a float. It just prints as 2.
Decimals: exact, for money and anything else that can't round
Don't use a float for money. A float is binary floating point, so
values like 0.1 and 0.2 aren't represented
exactly, and the error shows up the moment you do arithmetic. SCUA has a
separate decimal kind for this: an exact base-10
number. Write one with a d suffix.
print(0.1 + 0.2) -- float: not exact
print(0.1d + 0.2d) -- decimal: exact
print(type(0.1d))
Run it:
$ scua decimals.scua
0.30000000000000004
0.3
decimal
A decimal is its own kind, distinct from float.
Addition, subtraction, and multiplication are exact; an int
joins in freely (it widens to a decimal without losing anything). What
you can't do is mix a decimal with a float — that's a compile-time
error, on purpose, because silently blending an exact value with an
inexact one is how a float's rounding error sneaks into an exact
ledger:
let price = 19.99d
print(price * 3) -- 59.97, exact
print(price + 1) -- 20.99 (the int widens)
-- print(price + 0.5) -- error: exact decimal and inexact float don't mix
Division can't always be exact (a third of ten isn't a finite
decimal), so / rounds — half-to-even, the unbiased
"banker's" rule — and keeps a sensible number of places. When you want a
specific number of decimal places, say so:
import decimal
print(10.00d / 3d) -- 3.333333
print(decimal.round(10.00d / 3d, 2)) -- 3.33
print(decimal.div(10d, 3d, 4)) -- 3.3333
An int is still the right tool for whole things —
scores, counts, IDs, in-game gold — and it's exact 64-bit, so no
rounding worries there either. Reach for a decimal when you need exact
fractional values: tax rates, percentages, measurements,
anything where a float's drift is unacceptable. For actual money there's
a dedicated money
type built on the same exact arithmetic: an amount that carries its
currency (19.99 USD), so it prints with the right symbol,
splits without losing a cent, and won't let you add two different
currencies or a bare number by mistake.
#Big numbers: huge magnitudes for idle games
Idle and incremental games (Cookie Clicker and its kin) run on
numbers that grow without bound — 1.2e456 cookies, and
climbing. An int tops out around 9.2e18 and a
float hits infinity past 1.8e308, so neither
survives the late game. The big kind does: an inexact
base-10 number with effectively unbounded magnitude. You build one with
big(...), using a string for anything past the float range
so the digits never pass through (and overflow) a float:
let cookies = big("1.2e456")
print(cookies)
print(big(10) ** big(100)) -- ** is the genre's core move
print(big("1e100") * big("1e50"))
print(type(cookies))
Run it:
$ scua big.scua
1.2e456
1e100
1e150
big
A big is the opposite trade from a decimal: magnitude is unbounded, precision is not. It carries about 15 significant figures, which is all a 400-digit cookie count needs — so adding a far-smaller value is a no-op by design:
print(big("1e100") + 1) -- still 1e100; the +1 is 100 orders of magnitude too small to show
An int or float mixes into big arithmetic
freely (both widen in), and **, *,
/, +, -, and comparisons all
work. What a big won't do is mix with a decimal — that's a
compile-time error, because one is exact and the other isn't, and
blending them is meaningless. The rule of thumb:
big buys reach,
decimal/int buy exactness. Never put
money in a big (it would round away the cents); never put a cookie count
in an int (it would overflow). The idle example shows the whole loop —
clicking, upgrades, prestige multipliers — at idle-game scale.
#Which kind?
| You need | Use | Why |
|---|---|---|
| Whole numbers — scores, counts, IDs, gold | int |
exact 64-bit |
| Continuous values where small error is fine | float |
huge range, fast |
| Exact fractional values — money, rates | decimal |
no rounding error |
| Magnitudes past 10¹⁸ — idle/incremental games | big |
unbounded range |