Duke's Big Numbers 1.0
C++ and Blueprint libraries for performing math, analysis, and formatting with really large numbers (>10e308).
|
A full reference of operations supported by FBigInteger is located at Operations
Next, let's address the elephant in the room: Allocations. Allocations are the bane of every game engineer's existence, but they are an unfortunate fact of that existance.
FBigInteger is a variable-length type. As such it allocates memory to hold its numerical values. This has a couple of important implications:
Fortunately, as the buffer contained by FBigInteger is housed in an Unreal TArray<> and managed as a UPROPERTY(), there is little else for us to worry about in terms of memory management. The default destructor semantics properly deallocate the underlying arrays for us.
To avoid allocations from copying temporary values, mutating operations (methods ending in "In" or compound assignment operators) can be used on FBigIntegers, circumventing the immutable value semantics that FBigInteger operates under by default.
Exponents and roots can be particularly allocation-heavy, as is parsing and formatting.
String formatters will make a fast best guess effort to pre-allocate space in the destination string, rather than allocating space ad-hoc as characters are appended.
Another way to avoid temporary FBigInteger allocations and gain speed is to use the narrower overloads of operations, when provided. In addition to avoiding an allocation of a temporary FBigInteger to hold the value upcast from a native type, these overloads are generally able to take some shortcuts by being able to assume that one or more of the values being operated on will fit in a less expensive native type.
For example, if you are dividing an FBigInteger by a divisor, and you know that divisor will never overflow an UnsignedInteger, then you can gain quite a bit of efficiency by using the overload that directly takes an UnsignedInteger type as the divisor operand.
On modern systems with 64 byte cache lines, approximately every 154 or so powers of 10 (2^(2^9) or 512 bits) overflows a number to an additional cache line, eventually resulting in cache thrash when numbers are large enough to oversaturate all available cache lines. This is an unavoidable cost of modern multiple-precision integer math.
This means that overall application performance can suddenly drop off when numbers get large enough - the exact magnitude of number causing this to happen will depend on many factors including the rest of your application's workload.
Pick your largest expected value, test using numbers of that magnitude, and refine your application's performance accordingly.
Operations on FBigIntegers can come in several flavors - Static, Instance, Instance-modifying, Operator, Operator-modifying, and Blueprintable.
For ease of API use, DBN strives to expose all applicable operation flavors that make sense. In many cases, the implementations of different flavors are simply inline forwards or curries to a canonical implementation.
Throughout this documentation you may see references to an opcode notation for operations, like this:
Flavor:Static BI ← add BI BI
This is my way of imposing order on the chaos, creating a manageable assembly-like shorthand for operations and their parameters. It has helped me to organize this library and ensure consistency and completeness. You don't need to understand or use this opcode notation for DBN to be useful to you.
If you want to interpret and understand this notation, it should be somewhat intuitive on several fronts.
If you have any grounding in assembly, you will note that these bear a resemblance to assembler opcodes and operands. That is a correct analogy. Even though an operation may take several forms (have several flavors), it still basically has the same inputs and outputs. Some of the inputs and outputs might redirect from different sources (such as a reference to a left-hand side parameter passed to a static method vs. an implied this
value on an instance method). The opcode format seeks to help match up the different flavors of the same operation.
The basic format of an opcode is:
RT ← opcode O1 O2 ... ON
Where:
RT
is the return type of the operationopcode
is the mnemonic (1-6 characters) describing the nature of the operationO1
thru ON
are the operand types.The base operand types are one or two characters and are also color-coded in the HTML version of the documentation. These colors follow the colors used for the types in blueprints where possible. DBN has a limited vocabulary of types that it works with. The types can be modified with one or more modifiers.
Generally const references are inferred. For the first parameter, whether the reference is const depends on the flavor of the operation, with the mutating flavors being non-const.
In/out and output parameters are specifically denoted as references in the opcode using the familiar ampersand (&
) modifier.
Some operands are arrays, specifically TArray<>s. These are denoted using the subscript ([]
) modifier.
In rare cases, some opcodes may work with a TCHAR pointer or a reference to a TCHAR pointer. Pointers are denoted with the pointer (*
) modifier.
The opcode notation does not specify separate types for enums, and instead just uses the base type of the enum (usually U1
).
Opcodes follow C++ rules for overloading of return types - two opcodes that differ only by return type will have different mnemomics.
Opcode Type | English Type | C++ Type | Notes |
---|---|---|---|
0 | Void | void | Special value denoting void type or zero parameters |
B | Boolean | bool | |
I1 | Signed Byte | int8 | |
U1 | Byte | uint8 | |
I2 | Integer16 | int16 | |
U2 | Unsigned Integer16 | uint16 | |
I4 | Integer | int32 | 32 bit |
U4 | Unsigned Integer | uint32 | 32 bit |
I8 | Integer64 | int64 | 64 bit |
U8 | Unsigned Integer64 | uint64 | 64 bit |
F4 | Float | float | 32 bit |
F8 | Double | double | 64 bit |
CH | Character | TCHAR | |
S | String | FString | UE Type |
BI | Big Integer | FBigInteger | The star of the show |
BF | Big Integer Formatter | UBigIntegerFormatter | Base class for formatter types |
DF | Decimal Invariant Formatter | UDecimalInvariantFormatter | |
FF | Decimal Full Formatter | UDecimalFullFormatter | |
SF | Decimal Scaling Formatter | UDecimalScalingFormatter | |
HF | Hexadecimal Invariant Formatter | UHexadecimalInvariantFormatter | |
BG | Group Labeler | UGroupLabeler | Base class for group labeler types |
NG | Named Group Labeler | UNamedGroupLabeler | |
SG | Scientific Group Labeler | UScientificGroupLabeler | |
MG | SI Units Group Labeler | USIUnitsGroupLabeler | |
CG | Custom Group Labeler | UCustomGroupLabeler | |
KY | Key | FKey | UE Type |
CU | Culture | FCulture | UE Type |
RU | Decimal Number Formatting Rules | FDecimalNumberFormattingRules | UE Type |
OP | Decimal Number Formatting Options | FDecimalNumberFormattingOptions | UE Type |
I4& | Integer Ref | int32& | |
U4& | UnsignedInteger Ref | uint32& | |
I8& | Integer64 Ref | int64& | |
U8& | UnsignedInteger64 Ref | uint64& | |
CH& | Character Ref | TCHAR& | |
S& | String Ref | FString& | |
BI& | BigInteger Ref | FBigInteger& | |
U1[] | Byte Array | TArray<uint8> | |
U4[] | UnsignedInteger Array | TArray<uint32> | |
BI[] | BigInteger Array | TArray<FBigInteger> | |
CH* | Character Buffer | TCHAR* | |
CH*& | Character Buffer Ref | TCHAR*& |