rational: The Third Numeric Type Behind Integer Division
Divide an int by an int, and the result is neither int nor float but a character who almost never shows their face: rational. The official docs give it a page of its own. This piece explains what it is, why it exists, and how it forces you to deal with two hazards right at the Compile step.
1. What Does 5 / 2 Actually Equal
In plenty of older languages and engines, 5 / 2 simply equals 2 — the remainder quietly dropped, flattened toward zero, no notice given. The rule is simple and fast, and it has bankrolled countless bugs: a split reward comes up one share short, a progress bar never quite reaches 100%, a division involving negatives lands on the wrong side... developers often stare at a slightly-off number at runtime for ages before remembering that integer division was holding the knife.
Verse's answer is completely different: 5 / 2 equals... 5/2. Literally — it's an exact fraction, of type rational, numerator 5, denominator 2, not a shred of precision lost. The official docs give this type a page of its own: it is "the result type of int divided by int", and outside of that you will hardly ever meet it anywhere.
rational comes with some real personality: you can't just type one in — apart from spellings like X:rational = 5/2, where the value comes from integer division, you cannot conjure a rational out of thin air; nor does it join any further addition, subtraction, multiplication, or division. In practice it has essentially one job in the language: being fed into Floor() or Ceil() and traded back for an int. Floor() rounds toward negative infinity, Ceil() toward positive infinity — the older-language habit of "just chop the decimals off" must be written out in Verse as Floor(A / B), and because the division might not go through, the whole affair must plug into a Branch node (if).
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
rational_lab_device := class(creative_device):
OnBegin<override>()<suspends>:void =
# 7 / 2 = rational 7/2; rounding down gives 3, rounding up gives 4
if (Down := Floor(7 / 2), Up := Ceil(7 / 2)):
Print("Floor: {Down} Ceil: {Up}")
Graph translation: inside Event BeginPlay, one Branch (if) node checks two things at once — Floor(7 / 2) rounding down and Ceil(7 / 2) rounding up, joined by a comma (= and): only when both go through does execution enter Print String. It's a pattern you'll grow ever more used to after Lesson 9. Here both divisions go through, and the log prints Floor: 3 Ceil: 4.
2. Two Questions Forced to Compile Time
Put rational and "division might not go through" side by side, and you'll see the design forces everyone who writes a division to answer two questions at Compile time:
Question one: could the divisor be 0? The usual approach elsewhere: dividing by zero either crashes on the spot (int) or quietly slips you an "infinity" (float) — either way, you only find out once the game is running. Verse defines int division as a step that might not go through and won't let Compile pass unless it's plugged into a Branch node — "what if the divisor is zero" is no longer a pray-after-launch question but a can't-submit-code-until-you-answer question.
Question two: which way does the remainder round? Some approaches chop decimals toward zero, others floor toward negative infinity, and each camp considers the other's behavior a bug. Verse simply refuses to pick a side: division hands you the exact fraction, and the rounding direction is whichever Floor or Ceil you write with your own hand — in black and white right there in the graph, readable at a glance, no memorizing anyone's defaults.
Incidentally, Floor's "toward negative infinity" genuinely parts ways with the chop-toward-zero approach on negatives: -7 / 2 chopped toward zero is -3, while Verse's Floor(-7 / 2) rounds -3.5 down to -4. In countdowns, damage-splitting, and other divisions where negatives show up, that one-notch difference is an incident waiting to happen.
So why not just return a float? This is the most chewable step in the whole design. Floats are binary fractions and cannot represent most decimal ratios — the instant 1/3 is stored into a float, precision is lost for good, and every rounding afterward is a decision made on an already-inaccurate number. A rational stores the exact ratio; precision is lost only on the one line where you explicitly call Floor/Ceil — how much is lost, and in which direction, all signed off by you. The hazard isn't eliminated — it's put out in the open. That is Verse's abiding temperament: a decision that's yours to make, it will never quietly make for you. You've met the same temperament in Set nodes, in logic (true/false), and in all those "might not go through" checks.
One last engineering tip: if a function is wall-to-wall division and giving every single one its own Branch (if) feels long-winded, mark the whole function with the <decides> effect so the function itself becomes a step that might not go through — the divisions inside can then be written bare. The price: whoever calls it must use square brackets and place a Branch (if) on their side to catch the "didn't go through". RoundToDecimalPlaces on Lesson 9's first extra page uses exactly this trick.
After if (N := Floor(7 / 2)): succeeds, what are N's value and type?
Why does Verse make int division return a rational instead of just returning a float?
Sources
Compiled from the official documentation: Rational in Verse (Epic official docs) ↗ · Int in Verse (Epic official docs) ↗