Skip to content

Commit f25d5dc

Browse files
committed
Add FLIP for fixing numeric type rounding inconsistency
1 parent 17d888e commit f25d5dc

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
status: proposed
3+
flip: 343
4+
authors: Supun Setunga (supun.setunga@flowfoundation.org), Dieter Shirley (dete@flowfoundation.com)
5+
sponsor: Dieter Shirley (dete@flowfoundation.com)
6+
updated: 2025-08-28
7+
---
8+
9+
# FLIP 343: Fix numeric type rounding inconsistency
10+
11+
## Objective
12+
13+
Provide a well-defined, consistent behaviour for handling the least-significant digit in arithmetic operations for
14+
all Cadence numeric types.
15+
16+
## Motivation
17+
18+
Numeric types in Cadence have no documented rounding behaviour when performing division operations, or fractional
19+
multiplication operations, but does truncation in most cases.
20+
For example:
21+
22+
```Cadence
23+
Int8(5) / Int8(2) == Int8(2)
24+
Int8(-5) / Int8(2) == Int8(-2)
25+
Int8(5) / Int8(-2) == Int128(-2)
26+
Int8(-5) / Int8(-2) == Int8(2)
27+
28+
Int64(5) / Int64(2) == Int64(2)
29+
Int64(-5) / Int64(2) == Int64(-2)
30+
Int8(5) / Int8(-2) == Int128(-2)
31+
Int64(-5) / Int64(-2) == Int64(2)
32+
33+
UFix64(0.00000005) / UFix64(2.0) == UFix64(0.00000002)
34+
UFix64(0.00000005) * UFix64(0.5) == UFix64(0.00000002)
35+
```
36+
37+
However, for negative inputs, the types `Int`, `Int128`, `Int256` and `Fix64` provide different results compared to the
38+
previously mentioned types.
39+
40+
```Cadence
41+
Int128(5) / Int128(2) == Int128(2)
42+
Int128(-5) / Int128(2) == Int128(-3)
43+
Int128(5) / Int128(-2) == Int128(-2)
44+
Int128(-5) / Int128(-2) == Int128(3)
45+
46+
Int256(5) / Int256(2) == Int256(2)
47+
Int256(-5) / Int256(2) == Int256(-3)
48+
Int256(5) / Int256(-2) == Int256(-2)
49+
Int256(-5) / Int256(-2) == Int256(3)
50+
51+
Int(5) / Int(2) == Int(2)
52+
Int(-5) / Int(2) == Int(-3)
53+
Int(5) / Int(-2) == Int(-2)
54+
Int(-5) / Int(-2) == Int(3)
55+
56+
Fix64(0.00000005) / Fix64(2.0) == Fix64(0.00000002)
57+
Fix64(-0.00000005) / Fix64(2.0) == Fix64(-0.00000003)
58+
Fix64(0.00000005) / Fix64(-2.0) == Fix64(-0.00000002)
59+
Fix64(-0.00000005) / Fix64(-2.0) == Fix64(0.00000003)
60+
```
61+
62+
This difference in behavior has been due to the use of different implementations for the numeric types.
63+
All integer types that have a length of 64-bit or less (`[U]Int8`, `[U]Int16`, `[U]Int32`, `[U]Int64`) are backed by
64+
Go-lang primitive types, whose division truncates the least-significant digit.
65+
66+
On the other hand, integer types that have a length of 128-bit or more (`[U]Int128`, `[U]Int256`, `[U]Int`),
67+
and fixed-point types (`[U]Fix64`) have been backed by https://pkg.go.dev/math/big standard library, and have been using
68+
[Int.Div(...)](https://pkg.go.dev/math/big#Int.Div) for division, which uses [Euclidean division](https://en.wikipedia.org/wiki/Euclidean_division),
69+
unlike Go-primitives.
70+
71+
This has resulted in the above inconsistency that we are seeing.
72+
73+
## User Benefit
74+
75+
Developers get a consistent behaviour for arithmetic operations for all numeric types.
76+
77+
## Design Proposal
78+
79+
This FLIP proposes to use the truncation for the least-significant digit for all arithmetic operations in Cadence.
80+
As such, while `[U]Int8`, `[U]Int16`, `[U]Int32`, `[U]Int64`, and `UFix64` remains unchanged, `[U]Int128`, `[U]Int256`,
81+
`[U]Int`, and `Fix64` would now behave slightly differently than they used to be, and would give the new results as
82+
follows:
83+
84+
```cadence
85+
Int128(5) / Int128(2) == Int128(2)
86+
Int128(-5) / Int128(2) == Int128(-2)
87+
Int128(5) / Int128(-2) == Int128(-2)
88+
Int128(-5) / Int128(-2) == Int128(2)
89+
90+
Int256(5) / Int256(2) == Int256(2)
91+
Int256(-5) / Int256(2) == Int256(-2)
92+
Int256(5) / Int256(-2) == Int256(-2)
93+
Int256(-5) / Int256(-2) == Int256(2)
94+
95+
Int(5) / Int(2) == Int(2)
96+
Int(-5) / Int(2) == Int(-2)
97+
Int(5) / Int(-2) == Int(-2)
98+
Int(-5) / Int(-2) == Int(2)
99+
100+
Fix64(0.00000005) / Fix64(2.0) == Fix64(0.00000002)
101+
Fix64(-0.00000005) / Fix64(2.0) == Fix64(-0.00000002)
102+
Fix64(0.00000005) / Fix64(-2.0) == Fix64(-0.00000002)
103+
Fix64(-0.00000005) / Fix64(-2.0) == Fix64(0.00000002)
104+
```
105+
106+
Note that this suggested change would only impact the **division (and saturation division) of negative values**
107+
for the above types.
108+
Division of non-negative numbers, as well as all the other arithmetics on both negative and non-negative numbers,
109+
would remain unchanged.
110+
111+
### Drawbacks
112+
113+
This would impact developers who may have been relying on the old behaviour for the division of negative numbers.
114+
115+
### Performance Implications
116+
117+
None.
118+
119+
### Dependencies
120+
121+
None.
122+
123+
### Engineering Impact
124+
125+
Implementing a new and efficient fixed-point representation is non-trivial.
126+
127+
### Compatibility
128+
129+
Given this change the behaviour for division for negative numbers, it'll be backward incompatible.
130+
131+
### User Impact
132+
133+
This would impact users who may have been relying on the old behaviour for the division of negative numbers.
134+
135+
## Related Issues
136+
137+
None.
138+
139+
## Implementation
140+
Draft implementation can be found at: https://github.com/onflow/cadence/pull/4184

0 commit comments

Comments
 (0)