Open lluki opened 1 year ago
See also this slack discussion: https://materializeinc.slack.com/archives/C02PPB50ZHS/p1664977591417439
While rug
/gmp
is not an option for use due to licensing, there appear to be some alternatives we could use:
astro-float
provides benchmark results that show it is significantly slower than rug
, unfortunately: https://github.com/stencillogic/bigfloat-bench. I couldn't find anything on dashu
but I wouldn't hold my breath. And we also don't know how much slower those libraries are than imprecise float math. But still, if we value correctness over efficiency, we should give those a try.
I would like to consider implementing/porting Ahrens's design; the accumulator is kind of complex but I think not actually that big a deal given that there are a variety of well-defined tests and two MIT-licensed implementations we can study.
If we can resolve the concern Lukas raised above, I agree that sounds like the best solution. If it turns out that implementing a Rust port is too hard, we could alternatively attempt a wrapper around the C++ port instead.
Note that reproducible accumulation does not mean the accumulator behaves like a a group, i.e.: accum_old = accum; accum += big; accum -= big; assert(accum_old == accum) is not guaranteed. But what is guaranteed is that the order additions will yield the bitwise same result (unlike a double). It's not clear to me if these guarantees are enough.
Looking at the code in reduce.rs ISTM that the accumulator only needs to be a semi group, so maybe we are good?
After discussing this in timely office hours, I think arbitrary precision floats would probably be fine, and less work than implementing Ahrens and making sure it works for all operations (though I would be happy to do this work). I had previously considered suggesting https://bellard.org/libbf/ in its infinite-precision mode, but then thought we needed a library for arbitrary precision rationals; however I suspect infinite precision floats are fine, too, on thinking about it. And I see that dashu supports rationals and floats anyway, so we should probably just use this.
What version of Materialize are you using?
v0.27.0-alpha.23.post (469bd67f9)
How did you install Materialize?
Built from source
What is the issue?
Floating point summation uses a fixed point accumulator that does not have double precision.
These are mostly follow up observations from #14695
The use of a fixed point accumulator results in the following observable problems:
Constant expression optimizer is not aware of limited summation domain
Unexpected wrapping arithmetic
Normal floating point arithmetic over/underflows to
+inf
/-inf
.On the other hand we wrap (note that 1e31 is essentially our max float):
Postgres in the same example produces an error:
Inconsistent use of wrapping arithmetic
Users expect that
+
behaves asSUM
, but it does not, as the former uses float arithmetic while the later uses fixed point:Effects on integer aggregation
Since bigint (uint8 and int8) perform their aggregation in double domain, the effects can be visible there as well. The following is planned as summation of t^2 in double domain. The plan is correct to account for the big intermediate values that might occur. However, due to our limited precision of double aggregation, this does not work correctly. In this example we calculate
9223372036854775807 ~= 2^63
squared which comfortable overflows our fixed precision aggregator. (math.log10((2**63)**2) ~= 38
and we support only 31)Possible Fixes
Thoughts for 3.3:
Note that reproducible accumulation does not mean the accumulator behaves like a a group, i.e.:
accum_old = accum; accum += big; accum -= big; assert(accum_old == accum)
is not guaranteed. But what is guaranteed is that the order additions will yield the bitwise same result (unlike a double). It's not clear to me if these guarantees are enough. It would certainly make each replica produce bitwise the same results, no matter the execution order.I think 3.2. or 3.3. are the way to go, they both offer the correct precision. 3.2 is faster to implement but slower (it might do memory allocation for example when summing). 3.3. is really nice, but the lack of a ready made implementation means some implementation effort.
Relevant log output
No response