oscbyspro / Numberick

An arithmagick overhaul in Swift
Apache License 2.0
15 stars 3 forks source link

Investigate flexible in-memory 2's complement #44

Closed oscbyspro closed 1 year ago

oscbyspro commented 1 year ago

I've decided to go with sign-magnitude, but I'm kind of curious how 2's complement stacks up. Perhaps I'll write a prototype when NBKFlexibleWidthKit is done.

oscbyspro commented 1 year ago

I bet I could do it in a week, because actual two's complement is so much more simpler than as-if two's complement.

oscbyspro commented 1 year ago

I hope in-memory two's complement compares well (or at least well enough) because it would be really nice if I did not have to manage the whole as-if two's complement circus.

oscbyspro commented 1 year ago

I suppose another thing that vexes me about sign-magnitude-as-if-two's-complement is that the digit type ends up being Int when you'd really want somethings like Signed<UInt>, which is how I modeled it in ANK.

oscbyspro commented 1 year ago

Having said that, signed two's complement normalization is quite clumsy compared to magnitude normalization.

oscbyspro commented 1 year ago

Storing the sign bit separately makes normalization as simple as the unsigned case, at the cost of bit castability. It's also weird and wasteful in the case of resizable integers (#46). Hm. I wonder if there's a better layout? Perhaps somehing like:

/// a collection of at least one digit
struct ResizableInteger<Digit> {
    var body: Array<Digit.Magnitude> // 0 or more elements
    var tail: Digit
}
/// or where the magnitude can have 0 bit width
struct ResizableInteger {
    var body: Magnitude
    var tail: Int

    struct Magnitude {
        var words: Array<UInt> // 0 or more elements
    }
}

Edit: I suppose these layouts are still implementable as arrays.

oscbyspro commented 1 year ago

I never got around to evaluating its viability, but I made two proximal tests/observations:

oscbyspro commented 1 year ago

Hm. I'll use the next couple of days to try it out.

oscbyspro commented 1 year ago

I haven't gotten to comparing performance yet, but there's something compelling about accessing its words with O(1) complexity. Is it as compelling as O(1) magnitude and negation? Dunno, but the un/signed symmetry is neat.

oscbyspro commented 1 year ago

Well, if it is viable then viability does not come easily.

// Scrappy 2's vs feature/NBKFlexibleWidth 
// Less is brrrrrr. Copying. (self, digit)

add: ( 78,  32) vs (91, 55)
sub: ( 79,  29) vs (92, 49)
mul: (164, 122) vs (88, 58) // (92, 64) with non-magnitude algorithms
div: ( 94,  46) vs (57, 20)

Lack of borrowing/consuming is perhaps significant.

oscbyspro commented 1 year ago

The inability to bit cast un/signed values (due to normalization) is also unfortunate.

oscbyspro commented 1 year ago

Seems high-maintenance, while most of sign-magnitude is paid for by the magnitude.

oscbyspro commented 1 year ago

I could write a generic FlexibleWidth\<Digit> model where Digit is either Int or UInt. That would be the most straightforward un/signed two's complement option (in my opinion). I've tried to avoid it, however, because there's only two interesting specializations of it, and it requires @inlinable everywhere. It wouldn't improve IntXL's performance, but it also wouldn't require two private helper protocols.

oscbyspro commented 1 year ago

I think I'll use the next couple of days trying out the generic approach. I'm fine with incrementally making IntXL viable, so long as doing so does not increase the project's complexity disproportionately.

oscbyspro commented 1 year ago

My first impression of a generic FlexibleWidth\<Digit> is not great, as the models are obviously quite different. One can underflow, the other cannot. They also normalize differently. I'm sure the fixed-width-but-resizable storage could be made generic, but I don't think it suits flexible-width.