gdc or ldc for faster programs?

Ali Çehreli acehreli at yahoo.com
Tue Jan 25 21:12:50 UTC 2022


On 1/25/22 11:52, Ali Çehreli wrote:

 > a program I wrote about spelling-out parts of a number

Here is the program as a single module:

module spellout.spellout;

// This program was written as a code kata to spell out
// certain parts of integers as in "1 million 2 thousand
// 42". Note that this way of spelling-out numbers is not
// grammatically correct in English.

// Returns a string that contains the partly spelled-out version
// of the parameter.
//
// You must copy the returned string when needed as this function
// uses the same internal buffer for all invocations of the same
// template instance.
auto spellOut(T)(in T number_) {
   import std.array : Appender;
   import std.string : strip;
   import std.traits : Unqual;
   import std.meta : AliasSeq;

   static Appender!(char[]) result;
   result.clear;

   // We treat these specially because the algorithm below does
   // 'number = -number' and calls the same implementation
   // function. The trouble is, for example, -int.min is still a
   // negative number.
   alias problematics = AliasSeq!(
     byte, "negative 128",
     short, "negative 32 thousand 768",
     int, "negative 2 billion 147 million 483 thousand 648",
     long, "negative 9 quintillion 223 quadrillion 372 trillion" ~
           " 36 billion 854 million 775 thousand 808");

   static assert((problematics.length % 2) == 0);

   static foreach (i, P; problematics) {
     static if (i % 2) {
       // This is a string; skip

     } else {
       // This is a problematic type
       static if (is (T == P)) {
         // Our T happens to be this problematic type
         if (number_ == T.min) {
           // and we are dealing with a problematic value
           result ~= problematics[i + 1];
           return result.data;
         }
       }
     }
   }

   auto number = cast(Unqual!T)number_; // Thanks 'in'! :p

   if (number == 0) {
     result ~= "zero";

   } else {
     if (number < 0) {
       result ~= "negative";
       static if (T.sizeof < int.sizeof) {
         // Being careful with implicit conversions. (See the dmd
         // command line switch -preview=intpromote)
         number = cast(T)(-cast(int)number);

       } else {
         number = -number;
       }
     }

     spellOutImpl(number, result);
   }

   return result.data.strip;
}

unittest {
   assert(1_001_500.spellOut == "1 million 1 thousand 500");
   assert((-1_001_500).spellOut ==
          "negative 1 million 1 thousand 500");
   assert(1_002_500.spellOut == "1 million 2 thousand 500");
}

import std.format : format;
import std.range : isOutputRange;

void spellOutImpl(T, O)(T number, ref O output)
if (isOutputRange!(O, char))
in (number > 0, format!"Invalid number: %s"(number)) {
   import std.range : retro;
   import std.format : formattedWrite;

   foreach (divider; dividers!T.retro) {
     const quotient = number / divider.value;

     if (quotient) {
       output.formattedWrite!" %s %s"(quotient, divider.word);
     }

     number %= divider.value;
   }
}

struct Divider(T) {
   T value;        // 1_000, 1_000_000, etc.
   string word;    // "thousand", etc
}

// Returns the words related with the provided size of an
// integral type. The parameter is number of bytes
// e.g. int.sizeof
auto words(size_t typeSize) {
   // This need not be recursive at all but it was fun using
   // recursion.
   final switch (typeSize) {
   case 1: return [ "" ];
   case 2: return words(1) ~ [ "thousand" ];
   case 4: return words(2) ~ [ "million", "billion" ];
   case 8: return words(4) ~ [ "trillion", "quadrillion", "quintillion" ];
   }
}

unittest {
   // These are relevant words for 'int' and 'uint' values:
   assert(words(4) == [ "", "thousand", "million", "billion" ]);
}

// Returns a Divider!T array associated with T
auto dividers(T)() {
   import std.range : array, enumerate;
   import std.algorithm : map;

   static const(Divider!T[]) result =
     words(T.sizeof)
     .enumerate!T
     .map!(t => Divider!T(cast(T)(10^^(t.index * 3)), t.value))
     .array;

   return result;
}

unittest {
   // Test a few entries
   assert(dividers!int[1] == Divider!int(1_000, "thousand"));
   assert(dividers!ulong[3] == Divider!ulong(1_000_000_000, "billion"));
}

void main() {
   version (test) {
     return;
   }

   import std.meta : AliasSeq;
   import std.stdio : writefln;
   import std.random : Random, uniform;
   import std.conv : to;

   static foreach (T; AliasSeq!(byte, ubyte, short, ushort,
                                int, uint, long, ulong)) {{
       // A few numbers for each type
       report(T.min);
       report((T.max / 4).to!T);  // Overcome int promotion for
                                  // shorter types because I want
                                  // to test with the exact type
                                  // e.g. for byte.
       report(T.max);
     }}

   enum count = 2_000_000;
   writefln!"Testing with %,s random numbers"(spellOut(count));

   // Use the same seed to be fair between compilations
   enum seed = 0;
   auto rnd = Random(seed);

   ulong totalLength;
   foreach (i; 0 .. count) {
     const number = uniform(int.min, int.max, rnd);
     const result = spellOut(number);
     totalLength += result.length;
   }

   writefln!("A meaningless number to prevent the compiler from" ~
             " removing the entire loop: %,s")(totalLength);
}

void report(T)(T number) {
   import std.stdio : writefln;
   writefln!"  %6s % ,s: %s"(T.stringof, number, spellOut(number));
}

Ali



More information about the Digitalmars-d-learn mailing list