oscbyspro / Ultimathnum

Binary arithmetic reimagined in Swift
Apache License 2.0
9 stars 1 forks source link

Some big vs infinite ideas #33

Closed oscbyspro closed 2 months ago

oscbyspro commented 3 months ago

It turns out that (#31) offers some big performance improvements, but the proposed size model has some complexity and ergonomic problems. The main problem, really, is that an infinite pointer-bit size cannot be modeled as an unsigned binary integer because it has too many edges (i.e. min-min, min-max, max-min, max-max). I am, therefore, pondering an alternative. I think it would be possible to preserve much of the current logic by setting the maximum binary integer and data integer size to UX.max + 1. In this case, a binary integer provides a size mask that the size is derived from:

protocol BinaryInteger {
    static var mask: UX { get }
}

extension SystemsInteger {
    static var size: UX { 
        Self.mask.incremented().unchecked()
    }
}

extension BinaryInteger {
    init?(size: (some BinaryInteger).Type) { ... } 
}

In this case, a size just an unsigned systems integer. Any current if !T.size.isInfinite { ... } would be replaced by if let size = UX(size: T.self) { ... }. Alternatively, one may add a similar but more aptly named getter.

oscbyspro commented 3 months ago

Hm. I suppose downshifting big unsigned integers is the problem with this approach since you'd get super significant zeros. This problem is solved by infinitely extending big unsigned integers. So big might simply be unviable.

oscbyspro commented 3 months ago

Could it be reasonable to have a finite size limit and infinite values? Like, what if this value only represents the binary integer body? The finite/infinite format can then be derived by checking whether it fits in a word. Hm. Imagine there's some signed-esque truncation past UX.max+1 bits. Like, what if any shift greater than UX.max is a full shift? Alternatively, what if UXL.max is not considered infinite? Like, imagine it's just 2^(2^64)-1 but with statically known signed-esque shift behavior.

oscbyspro commented 3 months ago

The above might be viable. Thinking more about it, perhaps IX.max+1 would be a better limit? In that case, the body's size fits in an unsigned word which then means that bit counting results do the same. Otherwise, they'd have to return a Fallible\<UX> to represent the most trivial cases. It would also make it possible to perform pointer-bit arithmetic near the limit without accidentally overflowing. Likewise, you could ask whether it's a power of two in standard pointer-bit fashion: size.count(1) == 1.

oscbyspro commented 3 months ago

That said, I suspect that proper infinity is still the most appropriate option and that it's mostly a matter of making the various interactions ergonomic rather than dreadful (#31).

oscbyspro commented 3 months ago

Here's another mask vs size thought. I could limit bit counting to systems integers and introduce Fallible\<IX> methods on DataInt. Generic binary integer code would then count bits through one of the views. The logarithmic order of infinity could be encoded by setting the mask's type to Shift\<Self>.

oscbyspro commented 3 months ago

Hm. The above comment deserves some investigation because it does not introduce any new concepts.

oscbyspro commented 3 months ago

I like the idea but mask is a terrible name. The above approach compare this value rather than a binary integer's size. As a result, it might be better to call it BinaryInteger.body and frame it as an identifier of sorts. The logarithmic infinity stuff can instead be represented by a Shift\<T>.max that just bit casts T.body.

oscbyspro commented 3 months ago

The above approach is pure greatness so far. Small, infallible, binary integer bit counts make a big difference.

oscbyspro commented 3 months ago

Hm. I believe that using the Count approach to represent all bit counts is the most consistent and that the ergonomics of it can be improved incrementally. While I like the idea of using systems integers in systems integer code, I also want there to be as little difference as possible between the most generic code and the most concrete code.