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