This example shows a lot of powerful features of D, generic programming with templates and function overloading like C++ but the syntax is cleaner. One good thing in D is that all the numeric basic types have fixed, defined bit-widths and can never change according to compiler or CPU architecture, whereas in C basic types can be anything which is very dangerous unless you use only the non-native types defined in the later standards in a header file.
This is a short routine to calculate powers of numbers. Looking at it I am pretty sure I can see some bad bugs, as some bits of it look nonsensical to me now. See if you can find them.
It also features something new of my own which I am adding to the D language courtesy of the power of magic functions provided by the GDC compiler. The likely() and unlikely() functions are hints to the compiler which tell it whether an if statement is likely to be true or the reverse. Therefore it generates far faster code. Depending on the CPU it either sorts out the hinting of the branch taken-vs-not-taken thing by using forward vs backward branches or using special instructions with explicit hints in some CPUs. I have found it extremely valuable in sorting out cases where there are if statements that are ridiculously unlikely, for example protective error checking or bounds checking, stuff that simply slows your routines down a lot. This could equally well be implemented in GCC C too as the low-level compiler magic is common.
// program power
alias uint64_t = ulong;
// ~~~ module likely v 1.00 - begin
version ( GNU )
version = GDC;
version ( GDC )
version = compiler_GDC;
bool likely()( in bool test_cond ) pure nothrow @safe
{
version ( compiler_GDC )
{
import gcc.builtins : __builtin_expect;
return cast(typeof(test_cond)) __builtin_expect( test_cond, true ); // __builtin_expect returns its own first argument, and actually returns a long
}
else
{ static assert( 0, "what compiler?" );
return test_cond;
}
}
bool unlikely()( in bool test_cond ) pure nothrow @safe
{
version ( compiler_GDC )
{
import gcc.builtins : __builtin_expect;
return cast(typeof(test_cond)) __builtin_expect( test_cond, false ); // __builtin_expect returns its own first argument, and actually returns a long
}
else
{ static assert( 0, "what compiler?" );
return test_cond;
}
}
// ~~~ module likely - end.
// +=================
// ~~~ module pow v 0.2 - begin
// unsigned exp
T pow(T, exp_ui_t )( in T x, in exp_ui_t p )
if ( is ( exp_ui_t == ulong ) || is ( exp_ui_t == uint ) )
in {
static assert( is ( typeof( x * x ) ) );
assert( p >= 0 );
}
out ( ret ) {
assert( ( p == 0 && ret == 1 ) || !( p == 0 ) );
}
body
{
if ( unlikely( p == 0 ) ) return 1;
if ( unlikely( p == 1 ) ) return x;
/*
if ( unlikely( x == 0 ) ) // fast-path opt, unnecessary
return x;
if ( unlikely( x == 1 ) ) // fast-path opt, unnecessary
return x;
*/
T s = x;
T v = 1;
for ( exp_ui_t i = p; i > 1; i >>= 1 )
{
v = ( i & 0x1 ) ? s * v : v;
s = s * s;
}
//assert( p > 1 && pow( x, p ) == ( p > 1 ? x * pow( x, p-1) : 1) );
return v * s;
}
template ToUnsignedType( T )
{
static if ( is (T == int ) || is (T == uint ))
alias ToUnsignedType = uint;
else static if ( is (T == long ) || is (T == ulong ))
alias ToUnsignedType = ulong;
else static assert( 0 );
}
// fp values, signed exp
T pow(T)( in T x, in int p )
if ( is( T == real ) || is( T == double ) || is ( T == float ) )
in {
assert( ! (x == 0 && p < 0 ) );
}
out {
}
body
{
alias ui_exp_t = ToUnsignedType!p;
const ui_exp_t u = cast(ui_exp_t) abs( p );
T r = pow( x, u );
if ( unlikely( p < 0 ) )
{
r = (cast(T) 1) / r;
}
return r;
}
// integer values, signed exp
T pow(T)( in T x, in int p )
if ( !( is( T == real ) || is( T == double ) || is ( T == float )) )
in {
assert( ! (x == 0 && p < 0 ) );
}
out {
}
body
{
if ( unlikely( p < 0 ) )
{ // if integral x
if ( unlikely( x == 1 ) )
return 1;
if ( unlikely( x == -1 ) )
return -1;
assert( 0 );
return 0; // must be unreachable, and in any case, must never drop through below
}
/* positive powers-only case */
assert ( p >= 0 );
const uint u = p;
return pow( x, u );
}
// ~~~ module pow end.
auto foo( in uint64_t x, in uint p )
{
static assert( pow( 2, 2) == 4);static assert( pow( 2, 3) == 8);
static assert( pow( 2, 5) == 32);
return pow( x, p );
}
Finally, a directive to the GDC compiler called assume() which tells the compiler that it can assume something is true at compile time, such as a value range restriction on a variable. This is like an assert() but assert()s are only enabled in debug builds whereas assume is always effective. It should make the compiler delete entire if statements where applicable.
void assume()( in bool condition ) nothrow @safe @nogc
{
debug assert( condition );
static if ( __traits( compiles, {enum bool test_ = condition; } ))
{
static assert( condition );
}
import gcc.builtins : __builtin_unreachable;
if (!condition)
{
__builtin_unreachable();
}
}
dstring assume( in dstring condition )
// usage eg mixin( assume( "a > 10" ) );
{
return "debug assert( " ~ condition ~ "); import gcc.builtins : __builtin_unreachable; ; if ( !(" ~ condition ~ ") ) __builtin_unreachable();";
}
bool foo( in int a )
{
assume(a > 10);
return a > 10;
}
bool bar( in int a )
{
mixin( assume( "a > 10" ) );
return a > 5;
}