This repository has been archived by the owner on May 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 689
/
fixed_point32.move
294 lines (275 loc) · 10.8 KB
/
fixed_point32.move
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/// Defines a fixed-point numeric type with a 32-bit integer part and
/// a 32-bit fractional part.
module std::fixed_point32 {
/// Define a fixed-point numeric type with 32 fractional bits.
/// This is just a u64 integer but it is wrapped in a struct to
/// make a unique type. This is a binary representation, so decimal
/// values may not be exactly representable, but it provides more
/// than 9 decimal digits of precision both before and after the
/// decimal point (18 digits total). For comparison, double precision
/// floating-point has less than 16 decimal digits of precision, so
/// be careful about using floating-point to convert these values to
/// decimal.
struct FixedPoint32 has copy, drop, store { value: u64 }
///> TODO: This is a basic constant and should be provided somewhere centrally in the framework.
const MAX_U64: u128 = 18446744073709551615;
/// The denominator provided was zero
const EDENOMINATOR: u64 = 0x10001;
/// The quotient value would be too large to be held in a `u64`
const EDIVISION: u64 = 0x20002;
/// The multiplied value would be too large to be held in a `u64`
const EMULTIPLICATION: u64 = 0x20003;
/// A division by zero was encountered
const EDIVISION_BY_ZERO: u64 = 0x10004;
/// The computed ratio when converting to a `FixedPoint32` would be unrepresentable
const ERATIO_OUT_OF_RANGE: u64 = 0x20005;
/// Multiply a u64 integer by a fixed-point number, truncating any
/// fractional part of the product. This will abort if the product
/// overflows.
public fun multiply_u64(val: u64, multiplier: FixedPoint32): u64 {
// The product of two 64 bit values has 128 bits, so perform the
// multiplication with u128 types and keep the full 128 bit product
// to avoid losing accuracy.
let unscaled_product = (val as u128) * (multiplier.value as u128);
// The unscaled product has 32 fractional bits (from the multiplier)
// so rescale it by shifting away the low bits.
let product = unscaled_product >> 32;
// Check whether the value is too large.
assert!(product <= MAX_U64, EMULTIPLICATION);
(product as u64)
}
spec multiply_u64 {
pragma opaque;
include MultiplyAbortsIf;
ensures result == spec_multiply_u64(val, multiplier);
}
spec schema MultiplyAbortsIf {
val: num;
multiplier: FixedPoint32;
aborts_if spec_multiply_u64(val, multiplier) > MAX_U64 with EMULTIPLICATION;
}
spec fun spec_multiply_u64(val: num, multiplier: FixedPoint32): num {
(val * multiplier.value) >> 32
}
/// Divide a u64 integer by a fixed-point number, truncating any
/// fractional part of the quotient. This will abort if the divisor
/// is zero or if the quotient overflows.
public fun divide_u64(val: u64, divisor: FixedPoint32): u64 {
// Check for division by zero.
assert!(divisor.value != 0, EDIVISION_BY_ZERO);
// First convert to 128 bits and then shift left to
// add 32 fractional zero bits to the dividend.
let scaled_value = (val as u128) << 32;
let quotient = scaled_value / (divisor.value as u128);
// Check whether the value is too large.
assert!(quotient <= MAX_U64, EDIVISION);
// the value may be too large, which will cause the cast to fail
// with an arithmetic error.
(quotient as u64)
}
spec divide_u64 {
pragma opaque;
include DivideAbortsIf;
ensures result == spec_divide_u64(val, divisor);
}
spec schema DivideAbortsIf {
val: num;
divisor: FixedPoint32;
aborts_if divisor.value == 0 with EDIVISION_BY_ZERO;
aborts_if spec_divide_u64(val, divisor) > MAX_U64 with EDIVISION;
}
spec fun spec_divide_u64(val: num, divisor: FixedPoint32): num {
(val << 32) / divisor.value
}
/// Create a fixed-point value from a rational number specified by its
/// numerator and denominator. Calling this function should be preferred
/// for using `Self::create_from_raw_value` which is also available.
/// This will abort if the denominator is zero. It will also
/// abort if the numerator is nonzero and the ratio is not in the range
/// 2^-32 .. 2^32-1. When specifying decimal fractions, be careful about
/// rounding errors: if you round to display N digits after the decimal
/// point, you can use a denominator of 10^N to avoid numbers where the
/// very small imprecision in the binary representation could change the
/// rounding, e.g., 0.0125 will round down to 0.012 instead of up to 0.013.
public fun create_from_rational(numerator: u64, denominator: u64): FixedPoint32 {
// If the denominator is zero, this will abort.
// Scale the numerator to have 64 fractional bits and the denominator
// to have 32 fractional bits, so that the quotient will have 32
// fractional bits.
let scaled_numerator = (numerator as u128) << 64;
let scaled_denominator = (denominator as u128) << 32;
assert!(scaled_denominator != 0, EDENOMINATOR);
let quotient = scaled_numerator / scaled_denominator;
assert!(quotient != 0 || numerator == 0, ERATIO_OUT_OF_RANGE);
// Return the quotient as a fixed-point number. We first need to check whether the cast
// can succeed.
assert!(quotient <= MAX_U64, ERATIO_OUT_OF_RANGE);
FixedPoint32 { value: (quotient as u64) }
}
spec create_from_rational {
pragma opaque;
include CreateFromRationalAbortsIf;
ensures result == spec_create_from_rational(numerator, denominator);
}
spec schema CreateFromRationalAbortsIf {
numerator: u64;
denominator: u64;
let scaled_numerator = (numerator as u128) << 64;
let scaled_denominator = (denominator as u128) << 32;
let quotient = scaled_numerator / scaled_denominator;
aborts_if scaled_denominator == 0 with EDENOMINATOR;
aborts_if quotient == 0 && scaled_numerator != 0 with ERATIO_OUT_OF_RANGE;
aborts_if quotient > MAX_U64 with ERATIO_OUT_OF_RANGE;
}
spec fun spec_create_from_rational(numerator: num, denominator: num): FixedPoint32 {
FixedPoint32{value: (numerator << 64) / (denominator << 32)}
}
/// Create a fixedpoint value from a raw value.
public fun create_from_raw_value(value: u64): FixedPoint32 {
FixedPoint32 { value }
}
spec create_from_raw_value {
pragma opaque;
aborts_if false;
ensures result.value == value;
}
/// Accessor for the raw u64 value. Other less common operations, such as
/// adding or subtracting FixedPoint32 values, can be done using the raw
/// values directly.
public fun get_raw_value(num: FixedPoint32): u64 {
num.value
}
/// Returns true if the ratio is zero.
public fun is_zero(num: FixedPoint32): bool {
num.value == 0
}
/// Returns the smaller of the two FixedPoint32 numbers.
public fun min(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
if (num1.value < num2.value) {
num1
} else {
num2
}
}
spec min {
pragma opaque;
aborts_if false;
ensures result == spec_min(num1, num2);
}
spec fun spec_min(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
if (num1.value < num2.value) {
num1
} else {
num2
}
}
/// Returns the larger of the two FixedPoint32 numbers.
public fun max(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
if (num1.value > num2.value) {
num1
} else {
num2
}
}
spec max {
pragma opaque;
aborts_if false;
ensures result == spec_max(num1, num2);
}
spec fun spec_max(num1: FixedPoint32, num2: FixedPoint32): FixedPoint32 {
if (num1.value > num2.value) {
num1
} else {
num2
}
}
/// Create a fixedpoint value from a u64 value.
public fun create_from_u64(val: u64): FixedPoint32 {
let value = (val as u128) << 32;
assert!(value <= MAX_U64, ERATIO_OUT_OF_RANGE);
FixedPoint32{value: (value as u64)}
}
spec create_from_u64 {
pragma opaque;
include CreateFromU64AbortsIf;
ensures result == spec_create_from_u64(val);
}
spec schema CreateFromU64AbortsIf {
val: num;
let scaled_value = (val as u128) << 32;
aborts_if scaled_value > MAX_U64;
}
spec fun spec_create_from_u64(val: num): FixedPoint32 {
FixedPoint32 {value: val << 32}
}
/// Returns the largest integer less than or equal to a given number.
public fun floor(num: FixedPoint32): u64 {
num.value >> 32
}
spec floor {
pragma opaque;
aborts_if false;
ensures result == spec_floor(num);
}
spec fun spec_floor(val: FixedPoint32): u64 {
let fractional = val.value % (1 << 32);
if (fractional == 0) {
val.value >> 32
} else {
(val.value - fractional) >> 32
}
}
/// Rounds up the given FixedPoint32 to the next largest integer.
public fun ceil(num: FixedPoint32): u64 {
let floored_num = floor(num) << 32;
if (num.value == floored_num) {
return floored_num >> 32
};
let val = ((floored_num as u128) + (1 << 32));
(val >> 32 as u64)
}
spec ceil {
pragma opaque;
aborts_if false;
ensures result == spec_ceil(num);
}
spec fun spec_ceil(val: FixedPoint32): u64 {
let fractional = val.value % (1 << 32);
let one = 1 << 32;
if (fractional == 0) {
val.value >> 32
} else {
(val.value - fractional + one) >> 32
}
}
/// Returns the value of a FixedPoint32 to the nearest integer.
public fun round(num: FixedPoint32): u64 {
let floored_num = floor(num) << 32;
let boundary = floored_num + ((1 << 32) / 2);
if (num.value < boundary) {
floored_num >> 32
} else {
ceil(num)
}
}
spec round {
pragma opaque;
aborts_if false;
ensures result == spec_round(num);
}
spec fun spec_round(val: FixedPoint32): u64 {
let fractional = val.value % (1 << 32);
let boundary = (1 << 32) / 2;
let one = 1 << 32;
if (fractional < boundary) {
(val.value - fractional) >> 32
} else {
(val.value - fractional + one) >> 32
}
}
// **************** SPECIFICATIONS ****************
spec module {} // switch documentation context to module level
spec module {
pragma aborts_if_is_strict;
}
}