solmath 0.2.0

Deterministic fixed-point math and quantitative finance for Solana: Greeks, IV, American KBI, NIG, TWAP, and DeFi primitives.
Documentation
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
use crate::constants::SCALE_I;

/// A double-word fixed-point value: true_value = hi + lo / SCALE.
///
/// hi carries the standard SCALE-precision result.
/// lo carries the sub-ULP residual from the computation that produced hi.
/// Invariant: |lo| < SCALE_I.
///
/// This enables error-free propagation through multiply chains:
/// instead of discarding rounding remainders, they accumulate in lo
/// and can be folded back when precision matters (e.g. pow, IV).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DoubleWord {
    hi: i128,
    lo: i128,
}

impl DoubleWord {
    /// Create from a standard-precision value (lo = 0).
    #[inline]
    pub const fn from_hi(hi: i128) -> Self {
        Self { hi, lo: 0 }
    }

    /// Internal constructor for values whose residual invariant is already established.
    #[inline]
    pub(crate) const fn new_raw(hi: i128, lo: i128) -> Self {
        Self { hi, lo }
    }

    /// Collapse to standard precision by rounding lo into hi.
    #[inline]
    pub fn to_i128(self) -> i128 {
        self.to_i128_at_scale(SCALE_I)
    }

    /// Collapse by rounding lo into hi using the provided scale.
    /// Use `to_i128()` for standard SCALE_I, this for HP or other scales.
    #[inline]
    pub(crate) fn to_i128_at_scale(self, scale: i128) -> i128 {
        let half = scale / 2;
        let abs_lo = self.lo.unsigned_abs();
        let correction = if abs_lo < half as u128 {
            0
        } else if abs_lo > half as u128 {
            self.lo.signum()
        } else if self.lo > 0 && self.hi >= 0 {
            1
        } else if self.lo < 0 && self.hi <= 0 {
            -1
        } else {
            // `hi` was already rounded away from zero and the opposite-sign
            // half-ULP residual records the exact tie. Applying another
            // correction here would undo the original rounding.
            0
        };
        self.hi + correction
    }

    /// Exact addition of two DoubleWord values.
    /// Carries overflow from lo into hi. Returns Err on hi overflow.
    #[allow(dead_code)]
    #[inline]
    pub(crate) fn checked_add(self, other: Self) -> Result<Self, crate::error::SolMathError> {
        let lo_sum = self.lo + other.lo;
        let carry = if lo_sum >= SCALE_I {
            1
        } else if lo_sum <= -SCALE_I {
            -1
        } else {
            0
        };
        // Try all safe association orders. The first pair can overflow even
        // when the carry cancels it and the exact three-term sum is in range.
        let hi = self
            .hi
            .checked_add(other.hi)
            .and_then(|h| h.checked_add(carry))
            .or_else(|| {
                self.hi
                    .checked_add(carry)
                    .and_then(|h| h.checked_add(other.hi))
            })
            .or_else(|| {
                other
                    .hi
                    .checked_add(carry)
                    .and_then(|h| h.checked_add(self.hi))
            })
            .ok_or(crate::error::SolMathError::Overflow)?;
        Ok(Self {
            hi,
            lo: lo_sum - carry * SCALE_I,
        })
    }

    #[allow(dead_code)]
    #[inline]
    pub const fn hi(self) -> i128 {
        self.hi
    }

    #[allow(dead_code)]
    #[inline]
    pub const fn lo(self) -> i128 {
        self.lo
    }
}

#[cfg(kani)]
mod verification {
    use super::*;
    use crate::constants::SCALE_HP;

    fn prove_collapse_is_half_ulp(scale: i128) {
        let hi: i128 = kani::any();
        let lo: i128 = kani::any();
        kani::assume(lo > -scale);
        kani::assume(lo < scale);

        let half = scale / 2;
        let magnitude = lo.unsigned_abs();
        let correction = if magnitude < half as u128 {
            0
        } else if magnitude > half as u128 {
            lo.signum()
        } else if lo > 0 && hi >= 0 {
            1
        } else if lo < 0 && hi <= 0 {
            -1
        } else {
            0
        };
        let expected = hi.checked_add(correction);
        kani::assume(expected.is_some());

        let actual = DoubleWord::new_raw(hi, lo).to_i128_at_scale(scale);
        let error_numerator = if correction == 0 {
            magnitude
        } else {
            scale as u128 - magnitude
        };

        assert_eq!(actual, expected.unwrap());
        assert!(error_numerator <= half as u128);
    }

    /// Prove collapsing every valid standard-scale residual rounds to nearest
    /// with ties away from zero and at most one-half output ULP of error.
    #[kani::proof]
    fn standard_collapse_is_half_ulp_for_every_valid_residual() {
        prove_collapse_is_half_ulp(SCALE_I);
    }

    /// Prove the same collapse property at the crate's high-precision scale.
    #[kani::proof]
    fn hp_collapse_is_half_ulp_for_every_valid_residual() {
        prove_collapse_is_half_ulp(SCALE_HP);
    }

    /// Prove exact double-word addition re-normalizes every pair of valid
    /// sub-ULP residuals back into `(-SCALE, SCALE)` whenever the high word is
    /// representable. This preserves the invariant required by later ULP
    /// collapse proofs.
    #[kani::proof]
    fn checked_add_preserves_the_sub_ulp_residual_invariant() {
        let a_hi: i128 = kani::any();
        let a_lo: i128 = kani::any();
        let b_hi: i128 = kani::any();
        let b_lo: i128 = kani::any();
        kani::assume(a_lo > -SCALE_I);
        kani::assume(a_lo < SCALE_I);
        kani::assume(b_lo > -SCALE_I);
        kani::assume(b_lo < SCALE_I);

        let a = DoubleWord::new_raw(a_hi, a_lo);
        let b = DoubleWord::new_raw(b_hi, b_lo);
        if let Ok(sum) = a.checked_add(b) {
            assert!(sum.lo.unsigned_abs() < SCALE_I as u128);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constants::SCALE_I;

    #[test]
    fn test_dw_from_hi() {
        let dw = DoubleWord::from_hi(42 * SCALE_I);
        assert_eq!(dw.hi, 42 * SCALE_I);
        assert_eq!(dw.lo, 0);
    }

    #[test]
    fn test_dw_to_i128_no_correction() {
        let dw = DoubleWord {
            hi: 5 * SCALE_I,
            lo: 0,
        };
        assert_eq!(dw.to_i128(), 5 * SCALE_I);
    }

    #[test]
    fn test_dw_to_i128_positive_correction() {
        // lo >= SCALE/2 should round hi up by 1
        let dw = DoubleWord {
            hi: 5 * SCALE_I,
            lo: SCALE_I / 2,
        };
        assert_eq!(dw.to_i128(), 5 * SCALE_I + 1);
    }

    #[test]
    fn test_dw_to_i128_negative_correction() {
        let dw = DoubleWord {
            hi: 5 * SCALE_I,
            lo: -SCALE_I / 2,
        };
        assert_eq!(dw.to_i128(), 5 * SCALE_I);
    }

    #[test]
    fn test_dw_to_i128_small_lo_no_correction() {
        // lo < SCALE/2 should not change hi
        let dw = DoubleWord {
            hi: 5 * SCALE_I,
            lo: SCALE_I / 2 - 1,
        };
        assert_eq!(dw.to_i128(), 5 * SCALE_I);
    }

    #[test]
    fn test_dw_add_simple() {
        let a = DoubleWord {
            hi: 3 * SCALE_I,
            lo: 100,
        };
        let b = DoubleWord {
            hi: 4 * SCALE_I,
            lo: 200,
        };
        let c = a.checked_add(b).unwrap();
        assert_eq!(c.hi, 7 * SCALE_I);
        assert_eq!(c.lo, 300);
    }

    #[test]
    fn test_dw_add_lo_carry() {
        let a = DoubleWord {
            hi: 3 * SCALE_I,
            lo: SCALE_I - 100,
        };
        let b = DoubleWord {
            hi: 4 * SCALE_I,
            lo: 200,
        };
        let c = a.checked_add(b).unwrap();
        assert_eq!(c.hi, 7 * SCALE_I + 1);
        assert_eq!(c.lo, 100);
    }

    #[test]
    fn test_dw_add_lo_negative_carry() {
        let a = DoubleWord {
            hi: 3 * SCALE_I,
            lo: -(SCALE_I - 100),
        };
        let b = DoubleWord {
            hi: 4 * SCALE_I,
            lo: -200,
        };
        let c = a.checked_add(b).unwrap();
        assert_eq!(c.hi, 7 * SCALE_I - 1);
        assert_eq!(c.lo, -100);
    }

    #[test]
    fn test_dw_invariant_lo_bounded() {
        // After add, |lo| < SCALE must hold
        // Use hi values that won't overflow when summed with carry
        let extremes = [
            DoubleWord {
                hi: 0,
                lo: SCALE_I - 1,
            },
            DoubleWord {
                hi: 0,
                lo: -(SCALE_I - 1),
            },
            DoubleWord {
                hi: 1_000 * SCALE_I,
                lo: SCALE_I - 1,
            },
            DoubleWord {
                hi: -1_000 * SCALE_I,
                lo: -(SCALE_I - 1),
            },
        ];
        for &a in &extremes {
            for &b in &extremes {
                let c = a.checked_add(b).unwrap();
                assert!(
                    c.lo.abs() < SCALE_I,
                    "lo invariant violated: a={:?}, b={:?}, result={:?}",
                    a,
                    b,
                    c
                );
            }
        }
    }

    #[test]
    fn test_dw_to_i128_roundtrip() {
        // from_hi(x).to_i128() == x for any x
        for x in [
            0i128,
            1,
            -1,
            SCALE_I,
            -SCALE_I,
            i128::MAX / 2,
            i128::MIN / 2,
        ] {
            assert_eq!(DoubleWord::from_hi(x).to_i128(), x);
        }
    }

    // ===== to_i128_at_scale tests =====

    #[test]
    fn test_dw_at_scale_matches_to_i128() {
        let cases = [
            DoubleWord {
                hi: 5 * SCALE_I,
                lo: 0,
            },
            DoubleWord {
                hi: 5 * SCALE_I,
                lo: SCALE_I / 2,
            },
            DoubleWord {
                hi: 5 * SCALE_I,
                lo: -SCALE_I / 2,
            },
            DoubleWord {
                hi: 5 * SCALE_I,
                lo: SCALE_I / 2 - 1,
            },
            DoubleWord { hi: 0, lo: 0 },
            DoubleWord {
                hi: -3 * SCALE_I,
                lo: 100,
            },
            DoubleWord {
                hi: -3 * SCALE_I,
                lo: -100,
            },
        ];
        for dw in cases {
            assert_eq!(
                dw.to_i128(),
                dw.to_i128_at_scale(SCALE_I),
                "Mismatch for {:?}",
                dw
            );
        }
    }

    #[test]
    fn test_dw_at_scale_hp() {
        use crate::constants::SCALE_HP;
        // lo = SCALE_HP/2 should round up by 1
        let dw = DoubleWord {
            hi: 5 * SCALE_HP,
            lo: SCALE_HP / 2,
        };
        assert_eq!(dw.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP + 1);

        // lo = SCALE_HP/2 - 1 should not round
        let dw2 = DoubleWord {
            hi: 5 * SCALE_HP,
            lo: SCALE_HP / 2 - 1,
        };
        assert_eq!(dw2.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP);
    }

    #[test]
    fn test_dw_at_scale_hp_negative() {
        use crate::constants::SCALE_HP;
        // Negative lo at HP scale
        let dw = DoubleWord {
            hi: 5 * SCALE_HP,
            lo: -SCALE_HP / 2,
        };
        assert_eq!(dw.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP);

        let dw2 = DoubleWord {
            hi: 5 * SCALE_HP,
            lo: -(SCALE_HP / 2 - 1),
        };
        assert_eq!(dw2.to_i128_at_scale(SCALE_HP), 5 * SCALE_HP);
    }

    #[test]
    fn test_dw_at_scale_hp_roundtrip() {
        use crate::constants::SCALE_HP;
        for x in [0i128, 1, -1, SCALE_HP, -SCALE_HP] {
            assert_eq!(DoubleWord::from_hi(x).to_i128_at_scale(SCALE_HP), x);
        }
    }

    #[test]
    fn test_dw_exact_half_ties_round_away_from_zero_once() {
        let pos = DoubleWord::new_raw(1, -SCALE_I / 2);
        let neg = DoubleWord::new_raw(-1, SCALE_I / 2);
        assert_eq!(pos.to_i128(), 1);
        assert_eq!(neg.to_i128(), -1);

        let pos_from_zero = DoubleWord::new_raw(0, SCALE_I / 2);
        let neg_from_zero = DoubleWord::new_raw(0, -SCALE_I / 2);
        assert_eq!(pos_from_zero.to_i128(), 1);
        assert_eq!(neg_from_zero.to_i128(), -1);
    }

    #[test]
    fn test_dw_add_carry_can_cancel_intermediate_overflow() {
        let a = DoubleWord::new_raw(i128::MAX, -SCALE_I / 2);
        let b = DoubleWord::new_raw(1, -SCALE_I / 2);
        let sum = a.checked_add(b).unwrap();
        assert_eq!(sum, DoubleWord::from_hi(i128::MAX));
    }
}