中文版 · Blockchain learning guide · Previous article
Blockchain learning · Extension BCX012

An integer result can be perfectly reproducible and still violate the application’s rule. Rounding down may prevent over-issuing a credit, yet under-reserve a resource budget. Repeating a rounded calculation can differ from rounding its combined total. Multiplying before dividing preserves some precision but can overflow an intermediate machine value.
This lesson audits a complete fictional resource meter. The task is to identify units, choose rounding from a stated inequality, reconcile a persistent remainder and distinguish mathematical correctness from machine-width feasibility. Every quantity is an inert classroom resource, with no price, money, borrowing, transfer or live contract action.
The calculations use exact integer and rational arithmetic checked locally. They are not compiled Solidity execution, an OpenZeppelin runtime test or a production safety proof. The purpose is an auditable accounting decision rather than a recommendation to round in one direction everywhere.
Choose your reading route
In this guide
- 1. Write the units beside every stored integer
- 2. Preserve the fraction until the policy decides
- 3. Choose direction from the promised inequality
- 4. Test capacity using demand rather than the rounded display
- 5. Compare one request with seven separate requests
- 6. Define a cumulative carry policy as a different rule
- 7. Reconcile all seven carry steps
- 8. Keep whole-resource allocation residuals separate
- 9. Check the intermediate product, not only the quotient
- 10. Understand what wide-product division can and cannot solve
- 11. Build an acceptance record with units and boundaries
- Exercise with worked answers
- Frequently asked questions
- Sources and further reading
Sections 1–3 establish the scales and rounding requirements. Sections 4–7 examine capacity, request partitioning and per-participant carry. Sections 8–10 separate whole-resource residuals from fractional carry and test intermediate width. The exercise includes worked answers. For broader numerical concepts, see Numerical Stability and Reliable Computation.
Back to contents · Next section
1. Write the units beside every stored integer
The meter converts resource U into resource V using a fixed conversion rule. Source quantities are stored in ticks, with ten source ticks per U. Destination quantities are stored with one hundred destination ticks per V. The rate integer is 25 with scale 100, representing 0.25 V per U.
These three scales have different jobs. Ten translates source ticks into U; one hundred translates V into destination ticks; the rate’s separate scale makes the stored integer 25 mean one quarter V per U. Equal numeric scales do not make their roles interchangeable.
The request is seven source ticks. Its exact destination amount is seven divided by ten U, multiplied by twenty-five divided by one hundred V per U, multiplied by one hundred destination ticks per V. The U and V labels cancel, leaving 17.5 destination ticks, or the exact fraction 35/2.
All inputs are nonnegative integers, the rate and scales stay fixed, and denominators are positive. We are using scaled-integer bookkeeping, not claiming that Solidity’s built-in fixed-point types implement this exercise. The Solidity 0.8.30 type reference documents the relevant integer and fixed-point limitations.
Back to contents · Next section
2. Preserve the fraction until the policy decides
Before any rounding, the unreduced numerator is 7×25×100=17,500 and the denominator is 10×100=1,000. Integer quotient and remainder are 17 and 500. The remainder represents 500/1,000 of a destination tick, not five hundred whole ticks.
For this fixed configuration, the conversion simplifies to source ticks multiplied by 25 and divided by 10. Seven therefore gives numerator 175, quotient 17 and remainder 5. The remainder now means 5/10 of a destination tick. Both forms describe the same half tick with different numerator scales.
A premature integer division of seven source ticks by ten gives zero. Continuing from that zero produces zero output and discards the entire fractional source quantity. This is a different operation from evaluating the exact fraction and then selecting an integer output.
Another error treats seven ticks as seven whole U. That gives 175 destination ticks, ten times the correct exact amount. No choice of floor or ceiling repairs a unit mistake introduced before rounding.
The review should therefore first establish the dimensioned expression, then its exact quotient and residual, and only then the integer policy. A plausible-looking final number cannot replace those steps.
Back to contents · Next section
3. Choose direction from the promised inequality
Policy I issues an integer credit that must not exceed the exact entitlement. For the seven-tick request, its requirement is issued credit no greater than 17.5. The largest permitted integer is 17, the floor. Issuing 18 violates this particular promise.
Policy R reserves an integer resource budget that must be at least the exact demand. Its requirement is reserved budget no less than 17.5. The smallest sufficient integer is 18, the ceiling. Reserving 17 violates this different promise.
Neither direction is universally safer. Each follows its own inequality. Calling both operations “conversion” does not make their acceptable outputs identical. An implementation should identify which operation it is performing before applying its numeric routine.
For these nonnegative inputs, typed integer division’s truncation toward zero agrees with floor. That observation must not be extended silently to signed negative values, nor confused with literal-only expressions that may retain fractional precision. The core worksheet deliberately avoids both cases.
If the exact result is already an integer, floor and ceiling agree. For example, six source ticks convert to 15 destination ticks exactly. An unconditional extra one would be wrong for the minimal-reservation policy in that case.
Back to contents · Next section
4. Test capacity using demand rather than the rounded display
Suppose only 17 destination ticks are available for reservation. The seven-source-tick request needs at least 18 integer destination ticks under Policy R, so it does not fit. Displaying a floor result of 17 cannot make the actual demand fit the declared rule.
To find the maximum admissible integer source request, require 25a/10 to be no greater than 17. Thus a must be no greater than 170/25, which is 6.8. The largest integer source quantity is 6.
Check both sides of the boundary: six source ticks require 15 destination ticks; seven require 17.5, whose minimum sufficient integer reservation is 18. The conclusion follows from the exact demand and input granularity, not from rounding the requested input to a convenient display.
This is a resource-admission example, not financial optimisation. No unused capacity is automatically assigned to anyone. The remaining two destination ticks after accepting six are simply outside this request under the supplied policy.
Back to contents · Next section
5. Compare one request with seven separate requests
Reset the scenario and remove the capacity limit. Keep the same total input of seven source ticks and the same fixed rate. A single one-tick request has exact output 2.5 destination ticks.
If each request is independently floored without carry, seven one-tick requests issue 2 each, totaling 14. A single seven-tick request issues floor(17.5)=17. The difference is three issued ticks despite equal total input.
If each request is independently rounded up for reservation, seven one-tick requests require 3 each, totaling 21. A single aggregate request requires ceiling(17.5)=18. Again, grouping changes the integer result.
These are descriptive partition-dependence tests. They are not instructions to manipulate a live service. A specification must decide whether each request is a separate accounting boundary, whether totals are combined, or whether a participant-specific remainder persists.
It would be incorrect to call the independent calculations arithmetically inconsistent. Each follows its declared local rounding rule. The policy question is whether their difference from aggregation is intended, acceptable and communicated.
Back to contents · Next section
6. Define a cumulative carry policy as a different rule
Now introduce a separate policy for one participant under the unchanged rate and scales. Keep a carried numerator c, initially zero, in units of one tenth of a destination tick. For a new input a, form n=25a+c, issue the integer quotient of n divided by 10, and retain the remainder as the next c.
The carry belongs to that participant and persists between the seven requests. The model does not reset it after each operation, move it to another participant or convert it into whole ticks. No rate change occurs during the sequence.
After any prefix of requests, the accounting invariant is: ten times total issued ticks plus current carry equals twenty-five times total source ticks. The carry must remain between zero and nine inclusive. This equality records fractional residuals without issuing more than the cumulative exact amount.
This is a new cumulative promise, not merely a faster implementation of independent rounding. A request can receive three ticks when its standalone conversion is 2.5 because it also uses that participant’s previously accumulated half tick. The cumulative result, rather than each request in isolation, is the relevant bound.
Back to contents · Next section
7. Reconcile all seven carry steps
For seven successive one-tick inputs, the issued amounts are 2, 3, 2, 3, 2, 3 and 2. The carries after those steps are 5, 0, 5, 0, 5, 0 and 5. Total issued ticks are 17 and the final carry is five tenths of a tick.
At step one, n=25, so issue 2 and retain 5. At step two, n=25+5=30, so issue 3 and retain 0. The pattern repeats because each new request contributes the same numerator 25.
After step seven, the invariant reads 10×17+5=175=25×7. The seventeen issued ticks and half-tick residual exactly account for the 17.5-tick cumulative entitlement. The final carry is not five additional destination ticks and must not be displayed as such.
If the carry were cleared silently at each step, the result would revert to the independent-floor total 14. If another participant’s request consumed the carry, ownership would change. Neither behaviour is authorized by the cumulative policy just defined.
A future rate or scale change would need an explicit migration or settlement rule for existing carry. The present arithmetic does not decide that rule. It is safer to mark that policy question unresolved than reuse the same integer c with a new denominator and pretend its meaning stayed constant.
Back to contents · Next section
8. Keep whole-resource allocation residuals separate
In an independent allocation task, a pool contains ten indivisible destination resources and three recipients have equal weights. Flooring each exact one-third allocation gives 3, 3 and 3. One whole resource remains retained and unallocated.
The conservation record is 10=3+3+3+1. The final one is not lost merely because it was not assigned. Its owner or custodian remains the supplied pool, and the worksheet does not invent a tie-breaker, beneficiary or future distribution schedule.
Rounding each recipient up to 4 would require twelve resources from a pool of ten. The desire to avoid under-allocation cannot authorize creating the missing two. Any alternative allocation policy has to satisfy its own total-capacity rule.
This residual is one whole resource. It differs from the conversion carry of five tenths of a tick. Both are called remainders informally, but their units, ownership and persistence rules are different. Combining them in one unlabelled field would erase those distinctions.
Back to contents · Next section
9. Check the intermediate product, not only the quotient
Consider another independent numeric case with unsigned 256-bit operands: x=2^200, y=2^100 and denominator d=2^100. The exact mathematical quotient xy/d is 2^200, which fits in uint256.
The intermediate product is 2^300, however, and requires 301 bits. It does not fit in uint256. A naive checked multiplication of runtime uint256 operands fails before the later division can reduce the result.
If a hypothetical implementation instead wraps that product modulo 2^256, the product becomes zero because 2^300 is divisible by 2^256. Dividing the wrapped zero by d yields zero, not 2^200. Disabling the check therefore does not preserve the intended computation.
The example is about runtime typed arithmetic. It is not a claim that a compiler must evaluate every literal-only expression through the same intermediate type. Our local test uses exact Python integers to model the mathematical and wrapping outcomes, not compiled Solidity execution.
A fitting final answer is necessary but insufficient evidence of a safe evaluation order. Inspect each intermediate width, denominator validity and final representability rather than stopping at the size of the quotient.
Back to contents · Next section
10. Understand what wide-product division can and cannot solve
The pinned OpenZeppelin Math v5.4.0 source includes mulDiv logic using a 512-bit intermediate product and a rounding overload. Its valid-result conditions still matter, including denominator and final output constraints.
Such an algorithm addresses the numerical problem of computing a quotient without discarding the high product bits. It does not decide whether the application needs floor or ceiling, whether x has the correct scale, or which participant owns a residual.
Likewise, a ceiling shortcut that first adds denominator minus one can overflow its addition in a bounded type. This article does not offer that expression as a universally safe implementation recipe. The selected routine and its preconditions require their own checked specification.
For the wide-product example, a correct full-precision method can produce 2^200. That conclusion is a mathematical feasibility statement supported by the source’s described purpose, not a report that its compiled code was executed in this lesson.
No library name can replace an application-level inequality. A correctly implemented floor routine used for a must-cover reservation still gives the wrong policy result for 17.5.
Back to contents · Next section
11. Build an acceptance record with units and boundaries
For each operation, record input units, source scale, rate numerator and scale, destination scale, exact rational result, integer output, rounding condition and residual meaning. Then record the state boundary: independent request, combined batch or cumulative participant ledger.
For cumulative accounting, add participant identity, previous carry, new carry, total input and total issued. Verify the invariant at every prefix, not only after the seventh request. A final total could conceal an intermediate carry assigned to the wrong person.
For implementation feasibility, record operand types, intermediate widths, denominator checks and final range. Keep these separate from the resource policy: a policy can be valid while a proposed arithmetic sequence overflows, or arithmetic can run successfully while implementing the wrong promise.
A bounded verdict is: “Seven source ticks correspond exactly to 17.5 destination ticks. The no-overissue policy issues 17; the must-cover policy reserves 18. Seventeen available ticks admit at most six source ticks. The declared cumulative carry policy issues 17 across seven one-tick requests and retains 5/10 of a tick.”
Add: “The ten-resource pool leaves one whole resource unallocated. The 2^300 intermediate does not fit uint256 even though its quotient does. These finite cases establish their stated results, not correctness for every possible input or production system.”
Back to contents · Next section
Exercise with worked answers
- What does the stored rate 25 mean? With scale 100, it means 0.25 V per U, not twenty-five destination ticks per arbitrary input unit.
- Why is seven divided by ten as an integer too early? It produces zero before the rate and destination scale are applied, losing the fractional source amount.
- Which integer satisfies the largest no-overissue result for 17.5? Seventeen. Which is the smallest sufficient reservation? Eighteen.
- Can seven source ticks fit seventeen available destination ticks under the reservation rule? No. The maximum integer source request is six.
- What are seven separate floor results versus an aggregate floor? Fourteen versus seventeen.
- What does final carry five mean in the reduced cumulative rule? Five tenths of one destination tick, not five whole ticks.
- Where does the allocation pool’s remaining one go? It remains in the pool, explicitly unallocated; no recipient is supplied.
- Does a uint256-sized quotient guarantee the naive multiplication fits? No. The 2^300 product overflows that width before division.
As an extension, process an eighth one-tick request under the same participant’s carry policy. Previous carry is 5, so n=30, issue 3 and retain 0. Total issued becomes 20, and 10×20+0=25×8. The equality is about this cumulative policy; it does not retroactively change the independent-rounding cases.
Back to contents · Next section
Frequently asked questions
Is this merely token display decimals?
No. Display scaling presents a stored count. Here a dimensioned rate converts between different resources, and an operation-specific policy selects an integer output and residual treatment.
Is rounding down always conservative?
Only relative to a specified inequality. It avoids over-issuing in Policy I but under-reserves the seven-tick demand in Policy R.
Does carrying a remainder automatically make allocation fair?
No. Ownership, persistence, resets and rate changes need explicit rules. Sharing carry across participants without such a rule can change who receives an eventual whole unit.
Does using a wide-product routine prove the application correct?
No. It addresses an arithmetic implementation constraint, not units, policy, identity or the complete range of valid application states.
Back to contents · Next section
Sources and further reading
- Solidity 0.8.30 types: typed integer division, checked arithmetic and fixed-point limitations; checked 12 October 2026.
- OpenZeppelin Math v5.4.0: wide-product division and rounding source; checked 12 October 2026, not described as the latest release or executed here.
- Numerical Stability and Reliable Computation: mathematical background; the present lesson adds a complete scaled-integer resource ledger.
Reliable integer accounting preserves not just the chosen output, but the units, inequality, remainder ownership and machine assumptions that make that output defensible.
