SCUA

Manual

Numbers

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 an int or float can 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.0 is 5 as 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