source code
// The Computer Language Benchmarks Game
// https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
//
// contributed by SirJosh3917
// loosely based on chapel-3, fsharpcore-6, java-3, gcc-1.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
unsafe class Program
{
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private static void Main(string[] args)
{
mpz_t temp1 = default;
mpz_t temp2 = default;
mpz_t accumulator = default;
mpz_t denominator = default;
mpz_t numerator = default;
var n = int.Parse(args[0]);
// use a buffer before writing to the console
// repeated writes to the console are expensive in my testing on
// windows (cmd.exe), at least. it *may* be different for linux
//
// create a buffer:
// 10 digits, 1 tab, 1 colon, length of digits, times the amount of rows.
// it's not exactly the size of the output, but it's big enough.
//
// we also manually handle it because muh nanoseconds.
char* builder = stackalloc char[(10 + 1 + 1 + args[0].Length) * n];
// maintain a pointer to the start of the buffer - we'll be writing and
// incrementing builder as we append characters
var builderStart = builder;
GmpBindings.mpz_init(&temp1);
GmpBindings.mpz_init(&temp2);
GmpBindings.mpz_init_set_ui(&accumulator, 0);
GmpBindings.mpz_init_set_ui(&denominator, 1);
GmpBindings.mpz_init_set_ui(&numerator, 1);
// body borrowed from the chapel-3 program
int digit;
for (int i = 0, k = 0; i < n;)
{
do
{
do
{
k++;
// next_Term() rountine
var k2 = k * 2 + 1;
GmpBindings.mpz_addmul_ui(&accumulator, &numerator, 2);
GmpBindings.mpz_mul_ui(&accumulator, &accumulator, k2);
GmpBindings.mpz_mul_ui(&denominator, &denominator, k2);
GmpBindings.mpz_mul_ui(&numerator, &numerator, k);
}
while (GmpBindings.mpz_cmp(&numerator, &accumulator) > 0);
digit = ExtractDigit(3, &temp1, &temp2,
&numerator, &accumulator, &denominator);
}
while (digit != ExtractDigit(4, &temp1, &temp2,
&numerator, &accumulator, &denominator));
// digit is a number from 0 to 9, so we can just append a single
// character here
*builder++ = (char)('0' + digit);
// comparison from java-3 program
if (++i % 10 == 0)
{
*builder++ = '\t';
*builder++ = ':';
// we pass in int.MaxValue to ignore bounds checking since we
// know that the buffer is large enough to handle any writes we
// may make
i.TryFormat(new Span<char>(builder, int.MaxValue),
out var charsWritten);
builder += charsWritten;
*builder++ = '\n';
}
// eliminate digit
GmpBindings.mpz_submul_ui(&accumulator, &denominator, digit);
GmpBindings.mpz_mul_ui(&accumulator, &accumulator, 10);
GmpBindings.mpz_mul_ui(&numerator, &numerator, 10);
}
// we have to use Console.Out instead of Console.WriteLine because
// Console.Out has a method for printing a ROS
Console.Out.Write(new ReadOnlySpan<char>(
builderStart, (int)(builder - builderStart)));
// a lot of the pidigit implementations have memory leaks, we don't ;)
GmpBindings.mpz_clear(&temp1);
GmpBindings.mpz_clear(&temp2);
GmpBindings.mpz_clear(&accumulator);
GmpBindings.mpz_clear(&denominator);
GmpBindings.mpz_clear(&numerator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining |
MethodImplOptions.AggressiveOptimization)]
private static int ExtractDigit(int nth, mpz_t* temp1, mpz_t* temp2,
mpz_t* numerator, mpz_t* accumulator, mpz_t* denominator)
{
// comment from gcc-1 source:
// joggling between tmp1_Pointer and tmp2_Pointer, so GMP won't have to
// use temp buffers
GmpBindings.mpz_mul_ui(temp1, numerator, nth);
GmpBindings.mpz_add(temp2, temp1, accumulator);
GmpBindings.mpz_tdiv_q(temp1, temp2, denominator);
return GmpBindings.mpz_get_ui(temp1);
}
}
// ported from fsharpcore-6
// GMP reference: https://gmplib.org/manual/Integer-Internals.html
[StructLayout(LayoutKind.Sequential)]
internal ref struct mpz_t
{
private int _alloc;
private int _size;
private IntPtr _ptr;
}
internal static unsafe class GmpBindings
{
// bindings from the Java_GMP_Wrapper, java-3.html and fsharpcore-6
[DllImport("gmp", EntryPoint = "__gmpz_init")]
internal extern static void mpz_init(mpz_t* x);
[DllImport("gmp", EntryPoint = "__gmpz_clear")]
internal extern static void mpz_clear(mpz_t* x);
[DllImport("gmp", EntryPoint = "__gmpz_add")]
internal extern static void mpz_add(
mpz_t* sum, mpz_t* augend, mpz_t* addend);
[DllImport("gmp", EntryPoint = "__gmpz_addmul_ui")]
internal extern static void mpz_addmul_ui(
mpz_t* sum, mpz_t* multiplier, uint multiplicand);
[DllImport("gmp", EntryPoint = "__gmpz_cmp")]
internal extern static int mpz_cmp(
mpz_t* firstNumber, mpz_t* secondNumber);
[DllImport("gmp", EntryPoint = "__gmpz_get_ui")]
internal extern static int mpz_get_ui(mpz_t* number);
[DllImport("gmp", EntryPoint = "__gmpz_init_set_ui")]
internal extern static void mpz_init_set_ui(
mpz_t* number, int newValue);
[DllImport("gmp", EntryPoint = "__gmpz_mul_ui")]
internal extern static void mpz_mul_ui(
mpz_t* product, mpz_t* multiplier, int multiplicand);
[DllImport("gmp", EntryPoint = "__gmpz_submul_ui")]
internal extern static void mpz_submul_ui(
mpz_t* difference, mpz_t* minuend, int subtrahend);
[DllImport("gmp", EntryPoint = "__gmpz_tdiv_q")]
internal extern static void mpz_tdiv_q(
mpz_t* quotient, mpz_t* dividend, mpz_t* divisor);
}