Skip to content

Commit

Permalink
✨ Add new functions and improve tests (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
cairoeth authored Sep 19, 2024
2 parents dfbac9d + 8ff602d commit eca01a2
Show file tree
Hide file tree
Showing 17 changed files with 565 additions and 817 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "lib/solidity-cdf"]
path = lib/solidity-cdf
url = https://github.com/fiveoutofnine/solidity-cdf
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
65 changes: 46 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,70 @@
# 🔎 solcdf
# 🔎 solgauss

Approximation of the cumulative distribution function (cdf) for arbitrary 18 decimal fixed point parameters `x`, `μ`, `σ` and a target absolute error of 1e-8.
[![CI][ci-badge]][ci-url]

## 🧪 Methodology
Solidity library for statistical functions rationally approximated with an error of less than 1 × 10<sup>-8</sup>, including `erfc`, `erfinv`, `erfcinv`, `ppf`, and `cdf`.

The complementary error function (`erfc`) is approximated rationally using a variant from the algorithm presented in [this](https://eprint.iacr.org/2020/552.pdf) paper. The rational polynomial is composed of a polynomial of degree 11 as numerator, and a polynomial of degree 4 as denominator.
## ⚙️ Installation

The coefficients of the rational polynomial are scaled by a factor of 2^96 and then codified as part of the `erfc` function. This scaling allows for the use of cheaper `sar` (shift arithmetic right) operations instead of more expensive `sdiv` (signed division) operations (former costs 3 gas, while latter 5). Additionally, this function always returns 0 when the input is more than `4.0523` as the maximum absolute error would not surpass the target error.
To install with [**Foundry**](https://github.com/foundry-rs/foundry):

The `erfc` and `cdf` functions are differentially fuzzed against Python and Javascript implementations to ensure that they provide an absolute error of less than 1e-8. These functions are also benchmarked against [`solstat`](https://github.com/primitivefinance/solstat), [`gud-cdf`](https://github.com/Philogy/gud-cdf), and [`solidity-cdf`](https://github.com/fiveoutofnine/solidity-cdf). It's important to note that the benchmark does not verify the precision of these external libraries, such as `solidity-cdf`, which has an error of more than 1e-8.
```sh
forge install cairoeth/solcdf
```

## ⛽ Gas Benchmarks

| Function | min | avg | median | max | calls |
|----------------------|------|------|--------|------|-------|
| cdf (solcdf) | 562 | 657 | 562 | 876 | 256 |
| cdf (solidity-cdf) | 540 | 671 | 540 | 962 | 256 |
| cdf (gud-cdf) | 781 | 815 | 781 | 917 | 256 |
| cdf (solstat) | 938 | 4279 | 5159 | 5159 | 256 |
To run the gas benchmark: `forge t --gas-report --fuzz-seed 0x123`

To run the gas benchmark: `forge t --gas-report`
### `cdf`

| Function | min | avg | median | max |
|--------------------|------|------|--------|------|
| cdf (solcdf) | 519 | 610 | 519 | 833 |
| cdf (solidity-cdf) | 492 | 617 | 492 | 914 |
| cdf (gud-cdf) | 704 | 736 | 704 | 841 |
| cdf (solstat) | 916 | 4258 | 5137 | 5137 |

### `erfc`

| Function | min | avg | median | max |
|-----------------|------|------|--------|------|
| erfc (solcdf) | 687 | 688 | 687 | 693 |
| erfc (gud-cdf) | 569 | 570 | 569 | 603 |
| erfc (solstat) | 4436 | 4453 | 4436 | 4543 |

### `erfcinv`

| Function | min | avg | median | max |
|----------|------|------|--------|------|
| erfcinv | 669 | 828 | 783 | 1847 |

### `erfinv`

| Function | min | avg | median | max |
|----------|------|------|--------|------|
| erfinv | 604 | 749 | 718 | 1796 |

### `ppf`

| Function | min | avg | median | max |
|----------|------|------|--------|------|
| ppf | 856 | 1687 | 2034 | 2034 |

## ✅ Tests

Run the following for the dependencies used in the (differential) tests:

### Python
1. `python -m venv venv && source venv/bin/activate`
2. `pip install mpmath`

### Javascript
1. `(cd differential && yarn)`

To run the tests: `forge t`

## 👇 Acknowledgements

This repository is inspired by or directly modified from many sources, primarily:

- [High-Precision Bootstrapping of RNS-CKKS Homomorphic Encryption Using Optimal Minimax Polynomial Approximation and Inverse Sine Function](https://eprint.iacr.org/2020/552): paper for algorithm variant
- [gud-cdf](https://github.com/Philogy/gud-cdf): codification script
- [gaussian](https://github.com/errcw/gaussian): javascript differential scripts

[ci-badge]: https://github.com/cairoeth/solgauss/actions/workflows/test.yml/badge.svg
[ci-url]: https://github.com/cairoeth/solgauss/actions/workflows/test.yml
10 changes: 0 additions & 10 deletions differential/cdf.ts

This file was deleted.

16 changes: 0 additions & 16 deletions differential/erfc.ts

This file was deleted.

14 changes: 14 additions & 0 deletions differential/erfcinv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from mpmath import floor, erfinv
import sys


if __name__ == "__main__":
x = int(sys.argv[1]) / 10**18
try:
_erfcinv = int(floor(erfinv(1 - x) * 10**18))
except:
_erfcinv = 0
if _erfcinv < 0:
# For negative values, use two's complement representation
_erfcinv = (1 << 256) + _erfcinv
print(f'0x{_erfcinv:064x}')
14 changes: 14 additions & 0 deletions differential/erfinv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from mpmath import erfinv, mpf, floor
import sys


if __name__ == "__main__":
x = mpf(sys.argv[1]) / 10**18
try:
_erfinv = int(floor(erfinv(x) * 10**18))
except:
_erfinv = 0
if _erfinv < 0:
# For negative values, use two's complement representation
_erfinv = (1 << 256) + _erfinv
print(f'0x{_erfinv:064x}')
19 changes: 0 additions & 19 deletions differential/package.json

This file was deleted.

17 changes: 17 additions & 0 deletions differential/ppf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from mpmath import floor, sqrt, erfinv
import sys


if __name__ == "__main__":
x = int(sys.argv[1]) / 10**18
u = int(sys.argv[2]) / 10**18
o = int(sys.argv[3]) / 10**18

try:
_ppf = int(floor((u-o*sqrt(2)*erfinv(1-(2*x)))*10**18))
except:
_ppf = 0
if _ppf < 0:
# For negative values, use two's complement representation
_ppf = (1 << 256) + _ppf
print(f'0x{_ppf:064x}')
15 changes: 0 additions & 15 deletions differential/tsconfig.json

This file was deleted.

Loading

0 comments on commit eca01a2

Please sign in to comment.