oscbyspro / Ultimathnum

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

BinaryInteger.arbitrary(...) #48

Closed oscbyspro closed 1 month ago

oscbyspro commented 1 month ago

BinaryInteger.init(load: DataInt) is great when you have contiguous elements to copy, but you also need a generic way of requesting uninitialized memory. The latter lets you generate contiguous elements and store them without temporary allocations. Efficient random number generation is an immediate use case (#45). I imagine something like the following:

protocol BinaryInteger {

    /// Creates a new instance by manually initializing memory.
    ///
    /// - Parameter count: The number of **uninitialized** elements that will be
    ///   passed to the `delegate`. It must not be negative or exceed the maximum
    ///   number of elements that fit in the body of this binary integer type.
    ///
    /// - Parameter appendix: The bit that extends the bit pattern initialized
    ///   by the `delegate`. This bit is significant when the given `count` is
    ///   less than the maximum number of elements that fit in the body of this
    ///   binary integer type.
    ///
    /// - Parameter delegate: A process that manually **initializes each element** 
    ///   passed to it. Note that binary integer elements are trivial by definition.
    ///
    /// This initializer is similar to `init(load: DataInt)` but it does not 
    /// need any intermediate allocations in the arbitrary integer case. The
    /// systems integer case may require constant `count` and `appendix` arguments
    /// for performance reasons.
    ///
    /// - Note: The elements may be either uninitialized or unspecified.
    ///
    @inlinable static func uninitialized(
        unchecked   count:    IX,
        repeating   appendix: Bit,
        initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> Void
    )   -> Self
}
oscbyspro commented 1 month ago

You can statically query the number of elements that fit in the body of a binary integer type, so an unchecked count is fine. I considered adding clamping behavior, but I don't really want unnecessary runtime checks for dynamic inputs. You can write a clamping function using an unchecked function, but you can't do the opposite.

oscbyspro commented 1 month ago

Note that the compiler is quite adept at folding inlinable constant arguments (#45).

oscbyspro commented 1 month ago

Hm. I'm still not sold on it. I think I'll simply start by prototyping some of the randomness (#45) utilities I need. I'll then evaluate how useful this is.

oscbyspro commented 1 month ago

One could also imagine an approach similar to doStuffIfAvailable(_:):

static func arbitrary(
    uninitialized count: IX, 
    repeating appendix: Bit, 
    initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> Void
) -> Optional<Self>

In this case, you'd statically branch on T.size.isInfinite beforehand:

static func random(...) -> Self {
    if !Self.size.isInfinite {
        // some systems integer code

    }   else {        
        return Self.arbitrary(...)!
    }
}
oscbyspro commented 1 month ago

The above might actually be preferred since it is isomorphic with the first approach:

static func uninitialized(
    unchecked   count:    IX, 
    repeating   appendix: Bit,
    initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> Void
)   -> Self {

    if !Self.size.isInfinite {
        // one might allocate it unsafely too...
        var instance = Self(repeating: appendix)
        body.withUnsafeMutableBinaryIntegerBody(...)
        return instance

    }   else {
        return Self.arbitrary(...)!
    }
}

Additionally, the arbitrary case might as well be optional in case of allocation failure.

oscbyspro commented 1 month ago

Another benefit of BinaryInteger.arbitrary(...) is that arbitrary integers support initialized prefixes, whereas systems integer don't. I could, therefore, require that the delegate returns an initialized prefix count at the end of its execution.

oscbyspro commented 1 month ago
protocol BinaryInteger {

    /// Creates a new instance by manually initializing memory, but only if
    /// this is an arbitrary binary integer type and the given arguments are
    /// valid.
    ///
    /// - Parameter count: The number of uninitialized elements that will be
    ///   passed to the `delegate`. It must not be negative or exceed the entropy
    ///   limit.
    ///
    /// - Parameter appendix: The bit that extends the bit pattern initialized
    ///   by the `delegate`. Its significance depends on the signedness of this
    ///   binary integer type.
    ///
    /// - Parameter delegate: A process that manually initializes a prefix in
    ///   the buffer passed to it. It must return the initialized prefix length
    ///   at the end of its execution. Note that `Void` is automatically
    ///   reinterpreted as the given `count` by a convenient function overload.
    ///
    @inlinable static func arbitrary(
        uninitialized  count:  IX,
        repeating   appendix:  Bit,
        initializer delegate: (MutableDataInt<Element.Magnitude>.Body) -> IX
    )   -> Optional<Self>
}
oscbyspro commented 1 month ago

Hm. The repeating: Bit part should also default to Bit.zero, like it does everywhere else.