Next: , Previous: , Up: .Net Interface   [Index]


13.1 MPIR.Net Feature Overview

MPIR.Net exposes the following main classes:

Class: HugeInt
Class: HugeRational
Class: HugeFloat
Class: MpirRandom

The standard operators are overloaded to allow arithmetic with these classes. For example,

void Calculate()
{
  using (var a = new HugeInt(1234))
  using (var b = new HugeInt("-5678"))
  using (var c = new HugeInt(a + b))
  {
    Debug.WriteLine("Result: {0}", c);
  }
}

MPIR.Net’s multi-precision classes implement IDisposable, and the recommended usage for local instances is as shown above, within a using clause to guarantee native memory clean-up when a variable is disposed.

References that go out of scope without having been disposed are subject to the normal .Net garbage collection, which in most cases invokes object finalizers, and those in turn deallocate native memory. Applications that don’t have memory pressure should work just fine either way, although deterministic disposal is a best practice.

Like MPIR’s native C++ Class Interface, MPIR.Net implements an expression like a.Value = b + c with a single call to the corresponding native mpz_add, without using a temporary for the b + c part. More complex expressions that do not have a single-call native implementation like a.Value = b*c + d*e, still use temporary variables. Importantly, a.Value = a + b*c and the like will utilize the native mpz_addmul, etc. Note that in all of the above cases the assignment syntax is to set the Value property; more on that below.

Another similarity of MPIR.Net with the C++ interface is the deferral of evaluation. All arithmetic operations and many methods produce an expression object rather than an immediate result. This allows expressions of arbitrary complexity to be built. They are not evaluated until the expression is assigned to a destination variable, or when calling a method that produces a primitive (non-MPIR.Net type) result. For example:

void Calculate()
{
  var a = new HugeInt(12345);
  var b = new HugeInt(67890);
  var sum = a + b;                // produces an expression
  var doubleSum = sum * 2;        // produces a new expression
  bool positive = doubleSum > 0;  // evaluates the doubleSum expression
  int sumSign = doubleSum.Sign(); // evaluates the doubleSum expression
  a.Value = doubleSum - 4;        // evaluates the doubleSum expression
}

Here the addition and multiplication in (a + b) * 2 are computed three times because they are part of an expression that is consumed by three destinations, positive, sumSign, and a. To avoid the triple addition, this method should be re-written as:

void Calculate()
{
  var a = new HugeInt(12345);
  var b = new HugeInt(67890);
  var sum = a + b;                      // produces an expression
  var doubleSum = new HugeInt(sum * 2); // evaluates the expression
  bool positive = doubleSum > 0;        // evaluates the > comparison
  int sumSign = doubleSum.Sign();       // computes the sign
  a.Value = doubleSum - 4;              // computes the subtraction
}

Now the result of (a + b) * 2 is computed once and stored in an intermediate variable, whose value is used in subsequent statements. This code can be shortened as follows without changing the internal calculation:

void Calculate()
{
  var a = new HugeInt(12345);
  var b = new HugeInt(67890);
  var doubleSum = new HugeInt((a + b) * 2); // evaluates the expression
  var positive = doubleSum > 0;             // evaluates the > comparison
  var sumSign = doubleSum.Sign();           // computes the sign
  a.Value = doubleSum - 4;                  // computes the subtraction
}

The main idiosyncrasy of MPIR.Net is its assignment pattern. MPIR.Net types are implemented as reference types with value semantics. Like .Net Strings, the objects themselves are just lightweight pointers to data allocated elsewhere. In this case, the data is in native memory. Unlike Strings, MPIR types are mutable.

Value semantics requires you to be able to code statements like a = b + c. However, .Net (outside of C++) does not allow overloading the assignment operator, while assigning references would necessitate some unnecessary duplication and extra memory allocations, require reliance on the garbage collector, and prevent the use of mpz_addmul and the like.

To solve this problem, MPIR.Net uses the property assignment. All MPIR.Net types have a Value property. The magic of this property is in its setter, which does what an overloaded assignment operator would do in C++. So you write a.Value = b + c to calculate the sum of b and c and store the result in the existing variable a. This seems to be as close to an overloaded assignment as you can get in .Net, but is fluent enough to become a quick habit, and additionally reinforces the concept that an existing object can change its value while reusing internally allocated memory.

Setting Value evaluates the expression being assigned. Since at this point the destination is known, mpz_addmul and similar can be recognized and invoked.

Reading this property is less interesting, as it’s equivalent to but wordier than using the reference itself, i.e. a + b is equivalent to a.Value + b.Value. However it is still useful for making possible constructs such as a.Value += 5, a.Value *= 10, etc.

If you absent-mindedly type a = b + c or a *= 10, these will not compile because there is no implicit conversion from an expression. If an implicit conversion were defined, such code would incur an extra allocation plus garbage collection, making it potentially slower than performing the same operations on a.Value. It would also not compile if the destination were a local variable defined in a using clause, as is the recommended practice for method-local instances.

Care should be taken with the construct var a = b;. While perfectly legal (and cannot be made otherwise) in .Net, this only creates a copy of the managed reference to the same MPIR.Net object, without any copying of the data. If b is subsequently disposed, referencing a will throw an error.

MPIR classes can be intermixed in expressions to some degree. For example, most arithmetic operations with rational operands will accept integers. Where mixed operations are defined in MPIR, they are also implemented in MPIR.Net. Floats, on the other hand, typically don’t accept operands of other types. There is some cost associated with creating a floating point instance out of an integer, which would not be evident if automatic promotion existed. Use explicit constructors to convert instances of one type to new instances of other types, or one of the SetTo() overloads to save the result into an existing instance.

MPIR classes can also be intermixed in expressions with primitive types. For 64-bit builds, this includes long and ulong, which correspond to an MPIR limb. For 32-bit builds, int and uint are the largest primitive types you can use. Smaller integer primitives can always be used because they will be promoted by .Net.

Conversions back from MPIR classes to primitive types aren’t done automatically, instead methods ToLong()/ToUlong() for 64-bit builds or ToInt()/ToUint() are provided. Integers also implement GetLimb().


Next: Building MPIR.Net, Previous: .Net Interface, Up: .Net Interface   [Index]