// Written in the D programming language. /** This module is a port of a growing fragment of the $(D_PARAM numeric) header in Alexander Stepanov's $(LINK2 https://en.wikipedia.org/wiki/Standard_Template_Library, Standard Template Library), with a few additions. Macros: Copyright: Copyright Andrei Alexandrescu 2008 - 2009. License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP erdani.org, Andrei Alexandrescu), Don Clugston, Robert Jacques, Ilya Yaroshenko Source: $(PHOBOSSRC std/numeric.d) */ /* Copyright Andrei Alexandrescu 2008 - 2009. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ module std.numeric; import std.complex; import std.math; import core.math : fabs, ldexp, sin, sqrt; import std.range.primitives; import std.traits; import std.typecons; /// Format flags for CustomFloat. public enum CustomFloatFlags { /// Adds a sign bit to allow for signed numbers. signed = 1, /** * Store values in normalized form by default. The actual precision of the * significand is extended by 1 bit by assuming an implicit leading bit of 1 * instead of 0. i.e. `1.nnnn` instead of `0.nnnn`. * True for all $(LINK2 https://en.wikipedia.org/wiki/IEEE_floating_point, IEE754) types */ storeNormalized = 2, /** * Stores the significand in $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers, * IEEE754 denormalized) form when the exponent is 0. Required to express the value 0. */ allowDenorm = 4, /** * Allows the storage of $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Positive_and_negative_infinity, * IEEE754 _infinity) values. */ infinity = 8, /// Allows the storage of $(LINK2 https://en.wikipedia.org/wiki/NaN, IEEE754 Not a Number) values. nan = 16, /** * If set, select an exponent bias such that max_exp = 1. * i.e. so that the maximum value is >= 1.0 and < 2.0. * Ignored if the exponent bias is manually specified. */ probability = 32, /// If set, unsigned custom floats are assumed to be negative. negativeUnsigned = 64, /**If set, 0 is the only allowed $(LINK2 https://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers, * IEEE754 denormalized) number. * Requires allowDenorm and storeNormalized. */ allowDenormZeroOnly = 128 | allowDenorm | storeNormalized, /// Include _all of the $(LINK2 https://en.wikipedia.org/wiki/IEEE_floating_point, IEEE754) options. ieee = signed | storeNormalized | allowDenorm | infinity | nan , /// Include none of the above options. none = 0 } private template CustomFloatParams(uint bits) { enum CustomFloatFlags flags = CustomFloatFlags.ieee ^ ((bits == 80) ? CustomFloatFlags.storeNormalized : CustomFloatFlags.none); static if (bits == 8) alias CustomFloatParams = CustomFloatParams!( 4, 3, flags); static if (bits == 16) alias CustomFloatParams = CustomFloatParams!(10, 5, flags); static if (bits == 32) alias CustomFloatParams = CustomFloatParams!(23, 8, flags); static if (bits == 64) alias CustomFloatParams = CustomFloatParams!(52, 11, flags); static if (bits == 80) alias CustomFloatParams = CustomFloatParams!(64, 15, flags); } private template CustomFloatParams(uint precision, uint exponentWidth, CustomFloatFlags flags) { import std.meta : AliasSeq; alias CustomFloatParams = AliasSeq!( precision, exponentWidth, flags, (1 << (exponentWidth - ((flags & flags.probability) == 0))) - ((flags & (flags.nan | flags.infinity)) != 0) - ((flags & flags.probability) != 0) ); // ((flags & CustomFloatFlags.probability) == 0) } /** * Allows user code to define custom floating-point formats. These formats are * for storage only; all operations on them are performed by first implicitly * extracting them to `real` first. After the operation is completed the * result can be stored in a custom floating-point value via assignment. */ template CustomFloat(uint bits) if (bits == 8 || bits == 16 || bits == 32 || bits == 64 || bits == 80) { alias CustomFloat = CustomFloat!(CustomFloatParams!(bits)); } /// ditto template CustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags = CustomFloatFlags.ieee) if (((flags & flags.signed) + precision + exponentWidth) % 8 == 0 && precision + exponentWidth > 0) { alias CustomFloat = CustomFloat!(CustomFloatParams!(precision, exponentWidth, flags)); } /// @safe unittest { import std.math.trigonometry : sin, cos; // Define a 16-bit floating point values CustomFloat!16 x; // Using the number of bits CustomFloat!(10, 5) y; // Using the precision and exponent width CustomFloat!(10, 5,CustomFloatFlags.ieee) z; // Using the precision, exponent width and format flags CustomFloat!(10, 5,CustomFloatFlags.ieee, 15) w; // Using the precision, exponent width, format flags and exponent offset bias // Use the 16-bit floats mostly like normal numbers w = x*y - 1; // Functions calls require conversion z = sin(+x) + cos(+y); // Use unary plus to concisely convert to a real z = sin(x.get!float) + cos(y.get!float); // Or use get!T z = sin(cast(float) x) + cos(cast(float) y); // Or use cast(T) to explicitly convert // Define a 8-bit custom float for storing probabilities alias Probability = CustomFloat!(4, 4, CustomFloatFlags.ieee^CustomFloatFlags.probability^CustomFloatFlags.signed ); auto p = Probability(0.5); } // Facilitate converting numeric types to custom float private union ToBinary(F) if (is(typeof(CustomFloatParams!(F.sizeof*8))) || is(F == real)) { F set; // If on Linux or Mac, where 80-bit reals are padded, ignore the // padding. import std.algorithm.comparison : min; CustomFloat!(CustomFloatParams!(min(F.sizeof*8, 80))) get; // Convert F to the correct binary type. static typeof(get) opCall(F value) { ToBinary r; r.set = value; return r.get; } alias get this; } /// ditto struct CustomFloat(uint precision, // fraction bits (23 for float) uint exponentWidth, // exponent bits (8 for float) Exponent width CustomFloatFlags flags, uint bias) if (isCorrectCustomFloat(precision, exponentWidth, flags)) { import std.bitmanip : bitfields; import std.meta : staticIndexOf; private: // get the correct unsigned bitfield type to support > 32 bits template uType(uint bits) { static if (bits <= size_t.sizeof*8) alias uType = size_t; else alias uType = ulong ; } // get the correct signed bitfield type to support > 32 bits template sType(uint bits) { static if (bits <= ptrdiff_t.sizeof*8-1) alias sType = ptrdiff_t; else alias sType = long; } alias T_sig = uType!precision; alias T_exp = uType!exponentWidth; alias T_signed_exp = sType!exponentWidth; alias Flags = CustomFloatFlags; // Perform IEEE rounding with round to nearest detection void roundedShift(T,U)(ref T sig, U shift) { if (shift >= T.sizeof*8) { // avoid illegal shift sig = 0; } else if (sig << (T.sizeof*8 - shift) == cast(T) 1uL << (T.sizeof*8 - 1)) { // round to even sig >>= shift; sig += sig & 1; } else { sig >>= shift - 1; sig += sig & 1; // Perform standard rounding sig >>= 1; } } // Convert the current value to signed exponent, normalized form void toNormalized(T,U)(ref T sig, ref U exp) { sig = significand; auto shift = (T.sizeof*8) - precision; exp = exponent; static if (flags&(Flags.infinity|Flags.nan)) { // Handle inf or nan if (exp == exponent_max) { exp = exp.max; sig <<= shift; static if (flags&Flags.storeNormalized) { // Save inf/nan in denormalized format sig >>= 1; sig += cast(T) 1uL << (T.sizeof*8 - 1); } return; } } if ((~flags&Flags.storeNormalized) || // Convert denormalized form to normalized form ((flags&Flags.allowDenorm) && exp == 0)) { if (sig > 0) { import core.bitop : bsr; auto shift2 = precision - bsr(sig); exp -= shift2-1; shift += shift2; } else // value = 0.0 { exp = exp.min; return; } } sig <<= shift; exp -= bias; } // Set the current value from signed exponent, normalized form void fromNormalized(T,U)(ref T sig, ref U exp) { auto shift = (T.sizeof*8) - precision; if (exp == exp.max) { // infinity or nan exp = exponent_max; static if (flags & Flags.storeNormalized) sig <<= 1; // convert back to normalized form static if (~flags & Flags.infinity) // No infinity support? assert(sig != 0, "Infinity floating point value assigned to a " ~ typeof(this).stringof ~ " (no infinity support)."); static if (~flags & Flags.nan) // No NaN support? assert(sig == 0, "NaN floating point value assigned to a " ~ typeof(this).stringof ~ " (no nan support)."); sig >>= shift; return; } if (exp == exp.min) // 0.0 { exp = 0; sig = 0; return; } exp += bias; if (exp <= 0) { static if ((flags&Flags.allowDenorm) || // Convert from normalized form to denormalized (~flags&Flags.storeNormalized)) { shift += -exp; roundedShift(sig,1); sig += cast(T) 1uL << (T.sizeof*8 - 1); // Add the leading 1 exp = 0; } else assert((flags&Flags.storeNormalized) && exp == 0, "Underflow occured assigning to a " ~ typeof(this).stringof ~ " (no denormal support)."); } else { static if (~flags&Flags.storeNormalized) { // Convert from normalized form to denormalized roundedShift(sig,1); sig += cast(T) 1uL << (T.sizeof*8 - 1); // Add the leading 1 } } if (shift > 0) roundedShift(sig,shift); if (sig > significand_max) { // handle significand overflow (should only be 1 bit) static if (~flags&Flags.storeNormalized) { sig >>= 1; } else sig &= significand_max; exp++; } static if ((flags&Flags.allowDenormZeroOnly)==Flags.allowDenormZeroOnly) { // disallow non-zero denormals if (exp == 0) { sig <<= 1; if (sig > significand_max && (sig&significand_max) > 0) // Check and round to even exp++; sig = 0; } } if (exp >= exponent_max) { static if (flags&(Flags.infinity|Flags.nan)) { sig = 0; exp = exponent_max; static if (~flags&(Flags.infinity)) assert(0, "Overflow occured assigning to a " ~ typeof(this).stringof ~ " (no infinity support)."); } else assert(exp == exponent_max, "Overflow occured assigning to a " ~ typeof(this).stringof ~ " (no infinity support)."); } } public: static if (precision == 64) // CustomFloat!80 support hack { ulong significand; enum ulong significand_max = ulong.max; mixin(bitfields!( T_exp , "exponent", exponentWidth, bool , "sign" , flags & flags.signed )); } else { mixin(bitfields!( T_sig, "significand", precision, T_exp, "exponent" , exponentWidth, bool , "sign" , flags & flags.signed )); } /// Returns: infinity value static if (flags & Flags.infinity) static @property CustomFloat infinity() { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; value.significand = 0; value.exponent = exponent_max; return value; } /// Returns: NaN value static if (flags & Flags.nan) static @property CustomFloat nan() { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; value.significand = cast(typeof(significand_max)) 1L << (precision-1); value.exponent = exponent_max; return value; } /// Returns: number of decimal digits of precision static @property size_t dig() { auto shiftcnt = precision - ((flags&Flags.storeNormalized) == 0); return shiftcnt == 64 ? 19 : cast(size_t) log10(1uL << shiftcnt); } /// Returns: smallest increment to the value 1 static @property CustomFloat epsilon() { CustomFloat one = CustomFloat(1); CustomFloat onePlusEpsilon = one; onePlusEpsilon.significand = onePlusEpsilon.significand | 1; // |= does not work here return CustomFloat(onePlusEpsilon - one); } /// the number of bits in mantissa enum mant_dig = precision + ((flags&Flags.storeNormalized) != 0); /// Returns: maximum int value such that 10max_10_exp is representable static @property int max_10_exp(){ return cast(int) log10( +max ); } /// maximum int value such that 2max_exp-1 is representable enum max_exp = exponent_max - bias - ((flags & (Flags.infinity | Flags.nan)) != 0) + 1; /// Returns: minimum int value such that 10min_10_exp is representable static @property int min_10_exp(){ return cast(int) log10( +min_normal ); } /// minimum int value such that 2min_exp-1 is representable as a normalized value enum min_exp = cast(T_signed_exp) -(cast(long) bias) + 1 + ((flags & Flags.allowDenorm) != 0); /// Returns: largest representable value that's not infinity static @property CustomFloat max() { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; value.exponent = exponent_max - ((flags&(flags.infinity|flags.nan)) != 0); value.significand = significand_max; return value; } /// Returns: smallest representable normalized value that's not 0 static @property CustomFloat min_normal() { CustomFloat value; static if (flags & Flags.signed) value.sign = 0; value.exponent = (flags & Flags.allowDenorm) != 0; static if (flags & Flags.storeNormalized) value.significand = 0; else value.significand = cast(T_sig) 1uL << (precision - 1); return value; } /// Returns: real part @property CustomFloat re() { return this; } /// Returns: imaginary part static @property CustomFloat im() { return CustomFloat(0.0f); } /// Initialize from any `real` compatible type. this(F)(F input) if (__traits(compiles, cast(real) input )) { this = input; } /// Self assignment void opAssign(F:CustomFloat)(F input) { static if (flags & Flags.signed) sign = input.sign; exponent = input.exponent; significand = input.significand; } /// Assigns from any `real` compatible type. void opAssign(F)(F input) if (__traits(compiles, cast(real) input)) { import std.conv : text; static if (staticIndexOf!(immutable F, immutable float, immutable double, immutable real) >= 0) auto value = ToBinary!(Unqual!F)(input); else auto value = ToBinary!(real )(input); // Assign the sign bit static if (~flags & Flags.signed) assert((!value.sign) ^ ((flags&flags.negativeUnsigned) > 0), "Incorrectly signed floating point value assigned to a " ~ typeof(this).stringof ~ " (no sign support)."); else sign = value.sign; CommonType!(T_signed_exp ,value.T_signed_exp) exp = value.exponent; CommonType!(T_sig, value.T_sig ) sig = value.significand; value.toNormalized(sig,exp); fromNormalized(sig,exp); assert(exp <= exponent_max, text(typeof(this).stringof ~ " exponent too large: " ,exp," > ",exponent_max, "\t",input,"\t",sig)); assert(sig <= significand_max, text(typeof(this).stringof ~ " significand too large: ",sig," > ",significand_max, "\t",input,"\t",exp," ",exponent_max)); exponent = cast(T_exp) exp; significand = cast(T_sig) sig; } /// Fetches the stored value either as a `float`, `double` or `real`. @property F get(F)() if (staticIndexOf!(immutable F, immutable float, immutable double, immutable real) >= 0) { import std.conv : text; ToBinary!F result; static if (flags&Flags.signed) result.sign = sign; else result.sign = (flags&flags.negativeUnsigned) > 0; CommonType!(T_signed_exp ,result.get.T_signed_exp ) exp = exponent; // Assign the exponent and fraction CommonType!(T_sig, result.get.T_sig ) sig = significand; toNormalized(sig,exp); result.fromNormalized(sig,exp); assert(exp <= result.exponent_max, text("get exponent too large: " ,exp," > ",result.exponent_max) ); assert(sig <= result.significand_max, text("get significand too large: ",sig," > ",result.significand_max) ); result.exponent = cast(result.get.T_exp) exp; result.significand = cast(result.get.T_sig) sig; return result.set; } ///ditto alias opCast = get; /// Convert the CustomFloat to a real and perform the relevant operator on the result real opUnary(string op)() if (__traits(compiles, mixin(op~`(get!real)`)) || op=="++" || op=="--") { static if (op=="++" || op=="--") { auto result = get!real; this = mixin(op~`result`); return result; } else return mixin(op~`get!real`); } /// ditto // Define an opBinary `CustomFloat op CustomFloat` so that those below // do not match equally, which is disallowed by the spec: // https://dlang.org/spec/operatoroverloading.html#binary real opBinary(string op,T)(T b) if (__traits(compiles, mixin(`get!real`~op~`b.get!real`))) { return mixin(`get!real`~op~`b.get!real`); } /// ditto real opBinary(string op,T)(T b) if ( __traits(compiles, mixin(`get!real`~op~`b`)) && !__traits(compiles, mixin(`get!real`~op~`b.get!real`))) { return mixin(`get!real`~op~`b`); } /// ditto real opBinaryRight(string op,T)(T a) if ( __traits(compiles, mixin(`a`~op~`get!real`)) && !__traits(compiles, mixin(`get!real`~op~`b`)) && !__traits(compiles, mixin(`get!real`~op~`b.get!real`))) { return mixin(`a`~op~`get!real`); } /// ditto int opCmp(T)(auto ref T b) if (__traits(compiles, cast(real) b)) { auto x = get!real; auto y = cast(real) b; return (x >= y)-(x <= y); } /// ditto void opOpAssign(string op, T)(auto ref T b) if (__traits(compiles, mixin(`get!real`~op~`cast(real) b`))) { return mixin(`this = this `~op~` cast(real) b`); } /// ditto template toString() { import std.format.spec : FormatSpec; import std.format.write : formatValue; // Needs to be a template because of https://issues.dlang.org/show_bug.cgi?id=13737. void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) { sink.formatValue(get!real, fmt); } } } @safe unittest { import std.meta; alias FPTypes = AliasSeq!( CustomFloat!(5, 10), CustomFloat!(5, 11, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), CustomFloat!(1, 7, CustomFloatFlags.ieee ^ CustomFloatFlags.signed), CustomFloat!(4, 3, CustomFloatFlags.ieee | CustomFloatFlags.probability ^ CustomFloatFlags.signed) ); foreach (F; FPTypes) { auto x = F(0.125); assert(x.get!float == 0.125F); assert(x.get!double == 0.125); x -= 0.0625; assert(x.get!float == 0.0625F); assert(x.get!double == 0.0625); x *= 2; assert(x.get!float == 0.125F); assert(x.get!double == 0.125); x /= 4; assert(x.get!float == 0.03125); assert(x.get!double == 0.03125); x = 0.5; x ^^= 4; assert(x.get!float == 1 / 16.0F); assert(x.get!double == 1 / 16.0); } } @system unittest { // @system due to to!string(CustomFloat) import std.conv; CustomFloat!(5, 10) y = CustomFloat!(5, 10)(0.125); assert(y.to!string == "0.125"); } @safe unittest { alias cf = CustomFloat!(5, 2); auto a = cf.infinity; assert(a.sign == 0); assert(a.exponent == 3); assert(a.significand == 0); auto b = cf.nan; assert(b.exponent == 3); assert(b.significand != 0); assert(cf.dig == 1); auto c = cf.epsilon; assert(c.sign == 0); assert(c.exponent == 0); assert(c.significand == 1); assert(cf.mant_dig == 6); assert(cf.max_10_exp == 0); assert(cf.max_exp == 2); assert(cf.min_10_exp == 0); assert(cf.min_exp == 1); auto d = cf.max; assert(d.sign == 0); assert(d.exponent == 2); assert(d.significand == 31); auto e = cf.min_normal; assert(e.sign == 0); assert(e.exponent == 1); assert(e.significand == 0); assert(e.re == e); assert(e.im == cf(0.0)); } // check whether CustomFloats identical to float/double behave like float/double @safe unittest { import std.conv : to; alias myFloat = CustomFloat!(23, 8); static assert(myFloat.dig == float.dig); static assert(myFloat.mant_dig == float.mant_dig); assert(myFloat.max_10_exp == float.max_10_exp); static assert(myFloat.max_exp == float.max_exp); assert(myFloat.min_10_exp == float.min_10_exp); static assert(myFloat.min_exp == float.min_exp); assert(to!float(myFloat.epsilon) == float.epsilon); assert(to!float(myFloat.max) == float.max); assert(to!float(myFloat.min_normal) == float.min_normal); alias myDouble = CustomFloat!(52, 11); static assert(myDouble.dig == double.dig); static assert(myDouble.mant_dig == double.mant_dig); assert(myDouble.max_10_exp == double.max_10_exp); static assert(myDouble.max_exp == double.max_exp); assert(myDouble.min_10_exp == double.min_10_exp); static assert(myDouble.min_exp == double.min_exp); assert(to!double(myDouble.epsilon) == double.epsilon); assert(to!double(myDouble.max) == double.max); assert(to!double(myDouble.min_normal) == double.min_normal); } // testing .dig @safe unittest { static assert(CustomFloat!(1, 6).dig == 0); static assert(CustomFloat!(9, 6).dig == 2); static assert(CustomFloat!(10, 5).dig == 3); static assert(CustomFloat!(10, 6, CustomFloatFlags.none).dig == 2); static assert(CustomFloat!(11, 5, CustomFloatFlags.none).dig == 3); static assert(CustomFloat!(64, 7).dig == 19); } // testing .mant_dig @safe unittest { static assert(CustomFloat!(10, 5).mant_dig == 11); static assert(CustomFloat!(10, 6, CustomFloatFlags.none).mant_dig == 10); } // testing .max_exp @safe unittest { static assert(CustomFloat!(1, 6).max_exp == 2^^5); static assert(CustomFloat!(2, 6, CustomFloatFlags.none).max_exp == 2^^5); static assert(CustomFloat!(5, 10).max_exp == 2^^9); static assert(CustomFloat!(6, 10, CustomFloatFlags.none).max_exp == 2^^9); static assert(CustomFloat!(2, 6, CustomFloatFlags.nan).max_exp == 2^^5); static assert(CustomFloat!(6, 10, CustomFloatFlags.nan).max_exp == 2^^9); } // testing .min_exp @safe unittest { static assert(CustomFloat!(1, 6).min_exp == -2^^5+3); static assert(CustomFloat!(5, 10).min_exp == -2^^9+3); static assert(CustomFloat!(2, 6, CustomFloatFlags.none).min_exp == -2^^5+1); static assert(CustomFloat!(6, 10, CustomFloatFlags.none).min_exp == -2^^9+1); static assert(CustomFloat!(2, 6, CustomFloatFlags.nan).min_exp == -2^^5+2); static assert(CustomFloat!(6, 10, CustomFloatFlags.nan).min_exp == -2^^9+2); static assert(CustomFloat!(2, 6, CustomFloatFlags.allowDenorm).min_exp == -2^^5+2); static assert(CustomFloat!(6, 10, CustomFloatFlags.allowDenorm).min_exp == -2^^9+2); } // testing .max_10_exp @safe unittest { assert(CustomFloat!(1, 6).max_10_exp == 9); assert(CustomFloat!(5, 10).max_10_exp == 154); assert(CustomFloat!(2, 6, CustomFloatFlags.none).max_10_exp == 9); assert(CustomFloat!(6, 10, CustomFloatFlags.none).max_10_exp == 154); assert(CustomFloat!(2, 6, CustomFloatFlags.nan).max_10_exp == 9); assert(CustomFloat!(6, 10, CustomFloatFlags.nan).max_10_exp == 154); } // testing .min_10_exp @safe unittest { assert(CustomFloat!(1, 6).min_10_exp == -9); assert(CustomFloat!(5, 10).min_10_exp == -153); assert(CustomFloat!(2, 6, CustomFloatFlags.none).min_10_exp == -9); assert(CustomFloat!(6, 10, CustomFloatFlags.none).min_10_exp == -154); assert(CustomFloat!(2, 6, CustomFloatFlags.nan).min_10_exp == -9); assert(CustomFloat!(6, 10, CustomFloatFlags.nan).min_10_exp == -153); assert(CustomFloat!(2, 6, CustomFloatFlags.allowDenorm).min_10_exp == -9); assert(CustomFloat!(6, 10, CustomFloatFlags.allowDenorm).min_10_exp == -153); } // testing .epsilon @safe unittest { assert(CustomFloat!(1,6).epsilon.sign == 0); assert(CustomFloat!(1,6).epsilon.exponent == 30); assert(CustomFloat!(1,6).epsilon.significand == 0); assert(CustomFloat!(2,5).epsilon.sign == 0); assert(CustomFloat!(2,5).epsilon.exponent == 13); assert(CustomFloat!(2,5).epsilon.significand == 0); assert(CustomFloat!(3,4).epsilon.sign == 0); assert(CustomFloat!(3,4).epsilon.exponent == 4); assert(CustomFloat!(3,4).epsilon.significand == 0); // the following epsilons are only available, when denormalized numbers are allowed: assert(CustomFloat!(4,3).epsilon.sign == 0); assert(CustomFloat!(4,3).epsilon.exponent == 0); assert(CustomFloat!(4,3).epsilon.significand == 4); assert(CustomFloat!(5,2).epsilon.sign == 0); assert(CustomFloat!(5,2).epsilon.exponent == 0); assert(CustomFloat!(5,2).epsilon.significand == 1); } // testing .max @safe unittest { static assert(CustomFloat!(5,2).max.sign == 0); static assert(CustomFloat!(5,2).max.exponent == 2); static assert(CustomFloat!(5,2).max.significand == 31); static assert(CustomFloat!(4,3).max.sign == 0); static assert(CustomFloat!(4,3).max.exponent == 6); static assert(CustomFloat!(4,3).max.significand == 15); static assert(CustomFloat!(3,4).max.sign == 0); static assert(CustomFloat!(3,4).max.exponent == 14); static assert(CustomFloat!(3,4).max.significand == 7); static assert(CustomFloat!(2,5).max.sign == 0); static assert(CustomFloat!(2,5).max.exponent == 30); static assert(CustomFloat!(2,5).max.significand == 3); static assert(CustomFloat!(1,6).max.sign == 0); static assert(CustomFloat!(1,6).max.exponent == 62); static assert(CustomFloat!(1,6).max.significand == 1); static assert(CustomFloat!(3,5, CustomFloatFlags.none).max.exponent == 31); static assert(CustomFloat!(3,5, CustomFloatFlags.none).max.significand == 7); } // testing .min_normal @safe unittest { static assert(CustomFloat!(5,2).min_normal.sign == 0); static assert(CustomFloat!(5,2).min_normal.exponent == 1); static assert(CustomFloat!(5,2).min_normal.significand == 0); static assert(CustomFloat!(4,3).min_normal.sign == 0); static assert(CustomFloat!(4,3).min_normal.exponent == 1); static assert(CustomFloat!(4,3).min_normal.significand == 0); static assert(CustomFloat!(3,4).min_normal.sign == 0); static assert(CustomFloat!(3,4).min_normal.exponent == 1); static assert(CustomFloat!(3,4).min_normal.significand == 0); static assert(CustomFloat!(2,5).min_normal.sign == 0); static assert(CustomFloat!(2,5).min_normal.exponent == 1); static assert(CustomFloat!(2,5).min_normal.significand == 0); static assert(CustomFloat!(1,6).min_normal.sign == 0); static assert(CustomFloat!(1,6).min_normal.exponent == 1); static assert(CustomFloat!(1,6).min_normal.significand == 0); static assert(CustomFloat!(3,5, CustomFloatFlags.none).min_normal.exponent == 0); static assert(CustomFloat!(3,5, CustomFloatFlags.none).min_normal.significand == 4); } @safe unittest { import std.math.traits : isNaN; alias cf = CustomFloat!(5, 2); auto f = cf.nan.get!float(); assert(isNaN(f)); cf a; a = real.max; assert(a == cf.infinity); a = 0.015625; assert(a.exponent == 0); assert(a.significand == 0); a = 0.984375; assert(a.exponent == 1); assert(a.significand == 0); } @system unittest { import std.exception : assertThrown; import core.exception : AssertError; alias cf = CustomFloat!(3, 5, CustomFloatFlags.none); cf a; assertThrown!AssertError(a = real.max); } @system unittest { import std.exception : assertThrown; import core.exception : AssertError; alias cf = CustomFloat!(3, 5, CustomFloatFlags.nan); cf a; assertThrown!AssertError(a = real.max); } @system unittest { import std.exception : assertThrown; import core.exception : AssertError; alias cf = CustomFloat!(24, 8, CustomFloatFlags.none); cf a; assertThrown!AssertError(a = float.infinity); } private bool isCorrectCustomFloat(uint precision, uint exponentWidth, CustomFloatFlags flags) @safe pure nothrow @nogc { // Restrictions from bitfield // due to CustomFloat!80 support hack precision with 64 bits is handled specially auto length = (flags & flags.signed) + exponentWidth + ((precision == 64) ? 0 : precision); if (length != 8 && length != 16 && length != 32 && length != 64) return false; // mantissa needs to fit into real mantissa if (precision > real.mant_dig - 1 && precision != 64) return false; // exponent needs to fit into real exponent if (1L << exponentWidth - 1 > real.max_exp) return false; // mantissa should have at least one bit if (precision == 0) return false; // exponent should have at least one bit, in some cases two if (exponentWidth <= ((flags & (flags.allowDenorm | flags.infinity | flags.nan)) != 0)) return false; return true; } @safe pure nothrow @nogc unittest { assert(isCorrectCustomFloat(3,4,CustomFloatFlags.ieee)); assert(isCorrectCustomFloat(3,5,CustomFloatFlags.none)); assert(!isCorrectCustomFloat(3,3,CustomFloatFlags.ieee)); assert(isCorrectCustomFloat(64,7,CustomFloatFlags.ieee)); assert(!isCorrectCustomFloat(64,4,CustomFloatFlags.ieee)); assert(!isCorrectCustomFloat(508,3,CustomFloatFlags.ieee)); assert(!isCorrectCustomFloat(3,100,CustomFloatFlags.ieee)); assert(!isCorrectCustomFloat(0,7,CustomFloatFlags.ieee)); assert(!isCorrectCustomFloat(6,1,CustomFloatFlags.ieee)); assert(isCorrectCustomFloat(7,1,CustomFloatFlags.none)); assert(!isCorrectCustomFloat(8,0,CustomFloatFlags.none)); } /** Defines the fastest type to use when storing temporaries of a calculation intended to ultimately yield a result of type `F` (where `F` must be one of `float`, `double`, or $(D real)). When doing a multi-step computation, you may want to store intermediate results as `FPTemporary!F`. The necessity of `FPTemporary` stems from the optimized floating-point operations and registers present in virtually all processors. When adding numbers in the example above, the addition may in fact be done in `real` precision internally. In that case, storing the intermediate `result` in $(D double format) is not only less precise, it is also (surprisingly) slower, because a conversion from `real` to `double` is performed every pass through the loop. This being a lose-lose situation, `FPTemporary!F` has been defined as the $(I fastest) type to use for calculations at precision `F`. There is no need to define a type for the $(I most accurate) calculations, as that is always `real`. Finally, there is no guarantee that using `FPTemporary!F` will always be fastest, as the speed of floating-point calculations depends on very many factors. */ template FPTemporary(F) if (isFloatingPoint!F) { version (X86) alias FPTemporary = real; else alias FPTemporary = Unqual!F; } /// @safe unittest { import std.math.operations : isClose; // Average numbers in an array double avg(in double[] a) { if (a.length == 0) return 0; FPTemporary!double result = 0; foreach (e; a) result += e; return result / a.length; } auto a = [1.0, 2.0, 3.0]; assert(isClose(avg(a), 2)); } /** Implements the $(HTTP tinyurl.com/2zb9yr, secant method) for finding a root of the function `fun` starting from points $(D [xn_1, x_n]) (ideally close to the root). `Num` may be `float`, `double`, or `real`. */ template secantMethod(alias fun) { import std.functional : unaryFun; Num secantMethod(Num)(Num xn_1, Num xn) { auto fxn = unaryFun!(fun)(xn_1), d = xn_1 - xn; typeof(fxn) fxn_1; xn = xn_1; while (!isClose(d, 0, 0.0, 1e-5) && isFinite(d)) { xn_1 = xn; xn -= d; fxn_1 = fxn; fxn = unaryFun!(fun)(xn); d *= -fxn / (fxn - fxn_1); } return xn; } } /// @safe unittest { import std.math.operations : isClose; import std.math.trigonometry : cos; float f(float x) { return cos(x) - x*x*x; } auto x = secantMethod!(f)(0f, 1f); assert(isClose(x, 0.865474)); } @system unittest { // @system because of __gshared stderr import std.stdio; scope(failure) stderr.writeln("Failure testing secantMethod"); float f(float x) { return cos(x) - x*x*x; } immutable x = secantMethod!(f)(0f, 1f); assert(isClose(x, 0.865474)); auto d = &f; immutable y = secantMethod!(d)(0f, 1f); assert(isClose(y, 0.865474)); } /** * Return true if a and b have opposite sign. */ private bool oppositeSigns(T1, T2)(T1 a, T2 b) { return signbit(a) != signbit(b); } public: /** Find a real root of a real function f(x) via bracketing. * * Given a function `f` and a range `[a .. b]` such that `f(a)` * and `f(b)` have opposite signs or at least one of them equals ±0, * returns the value of `x` in * the range which is closest to a root of `f(x)`. If `f(x)` * has more than one root in the range, one will be chosen * arbitrarily. If `f(x)` returns NaN, NaN will be returned; * otherwise, this algorithm is guaranteed to succeed. * * Uses an algorithm based on TOMS748, which uses inverse cubic * interpolation whenever possible, otherwise reverting to parabolic * or secant interpolation. Compared to TOMS748, this implementation * improves worst-case performance by a factor of more than 100, and * typical performance by a factor of 2. For 80-bit reals, most * problems require 8 to 15 calls to `f(x)` to achieve full machine * precision. The worst-case performance (pathological cases) is * approximately twice the number of bits. * * References: "On Enclosing Simple Roots of Nonlinear Equations", * G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61, * pp733-744 (1993). Fortran code available from $(HTTP * www.netlib.org,www.netlib.org) as algorithm TOMS478. * */ T findRoot(T, DF, DT)(scope DF f, const T a, const T b, scope DT tolerance) //= (T a, T b) => false) if ( isFloatingPoint!T && is(typeof(tolerance(T.init, T.init)) : bool) && is(typeof(f(T.init)) == R, R) && isFloatingPoint!R ) { immutable fa = f(a); if (fa == 0) return a; immutable fb = f(b); if (fb == 0) return b; immutable r = findRoot(f, a, b, fa, fb, tolerance); // Return the first value if it is smaller or NaN return !(fabs(r[2]) > fabs(r[3])) ? r[0] : r[1]; } ///ditto T findRoot(T, DF)(scope DF f, const T a, const T b) { return findRoot(f, a, b, (T a, T b) => false); } /** Find root of a real function f(x) by bracketing, allowing the * termination condition to be specified. * * Params: * * f = Function to be analyzed * * ax = Left bound of initial range of `f` known to contain the * root. * * bx = Right bound of initial range of `f` known to contain the * root. * * fax = Value of `f(ax)`. * * fbx = Value of `f(bx)`. `fax` and `fbx` should have opposite signs. * (`f(ax)` and `f(bx)` are commonly known in advance.) * * * tolerance = Defines an early termination condition. Receives the * current upper and lower bounds on the root. The * delegate must return `true` when these bounds are * acceptable. If this function always returns `false`, * full machine precision will be achieved. * * Returns: * * A tuple consisting of two ranges. The first two elements are the * range (in `x`) of the root, while the second pair of elements * are the corresponding function values at those points. If an exact * root was found, both of the first two elements will contain the * root, and the second pair of elements will be 0. */ Tuple!(T, T, R, R) findRoot(T, R, DF, DT)(scope DF f, const T ax, const T bx, const R fax, const R fbx, scope DT tolerance) // = (T a, T b) => false) if ( isFloatingPoint!T && is(typeof(tolerance(T.init, T.init)) : bool) && is(typeof(f(T.init)) == R) && isFloatingPoint!R ) in { assert(!ax.isNaN() && !bx.isNaN(), "Limits must not be NaN"); assert(signbit(fax) != signbit(fbx), "Parameters must bracket the root."); } do { // Author: Don Clugston. This code is (heavily) modified from TOMS748 // (www.netlib.org). The changes to improve the worst-cast performance are // entirely original. T a, b, d; // [a .. b] is our current bracket. d is the third best guess. R fa, fb, fd; // Values of f at a, b, d. bool done = false; // Has a root been found? // Allow ax and bx to be provided in reverse order if (ax <= bx) { a = ax; fa = fax; b = bx; fb = fbx; } else { a = bx; fa = fbx; b = ax; fb = fax; } // Test the function at point c; update brackets accordingly void bracket(T c) { R fc = f(c); if (fc == 0 || fc.isNaN()) // Exact solution, or NaN { a = c; fa = fc; d = c; fd = fc; done = true; return; } // Determine new enclosing interval if (signbit(fa) != signbit(fc)) { d = b; fd = fb; b = c; fb = fc; } else { d = a; fd = fa; a = c; fa = fc; } } /* Perform a secant interpolation. If the result would lie on a or b, or if a and b differ so wildly in magnitude that the result would be meaningless, perform a bisection instead. */ static T secant_interpolate(T a, T b, R fa, R fb) { if (( ((a - b) == a) && b != 0) || (a != 0 && ((b - a) == b))) { // Catastrophic cancellation if (a == 0) a = copysign(T(0), b); else if (b == 0) b = copysign(T(0), a); else if (signbit(a) != signbit(b)) return 0; T c = ieeeMean(a, b); return c; } // avoid overflow if (b - a > T.max) return b / 2 + a / 2; if (fb - fa > R.max) return a - (b - a) / 2; T c = a - (fa / (fb - fa)) * (b - a); if (c == a || c == b) return (a + b) / 2; return c; } /* Uses 'numsteps' newton steps to approximate the zero in [a .. b] of the quadratic polynomial interpolating f(x) at a, b, and d. Returns: The approximate zero in [a .. b] of the quadratic polynomial. */ T newtonQuadratic(int numsteps) { // Find the coefficients of the quadratic polynomial. immutable T a0 = fa; immutable T a1 = (fb - fa)/(b - a); immutable T a2 = ((fd - fb)/(d - b) - a1)/(d - a); // Determine the starting point of newton steps. T c = oppositeSigns(a2, fa) ? a : b; // start the safeguarded newton steps. foreach (int i; 0 .. numsteps) { immutable T pc = a0 + (a1 + a2 * (c - b))*(c - a); immutable T pdc = a1 + a2*((2 * c) - (a + b)); if (pdc == 0) return a - a0 / a1; else c = c - pc / pdc; } return c; } // On the first iteration we take a secant step: if (fa == 0 || fa.isNaN()) { done = true; b = a; fb = fa; } else if (fb == 0 || fb.isNaN()) { done = true; a = b; fa = fb; } else { bracket(secant_interpolate(a, b, fa, fb)); } // Starting with the second iteration, higher-order interpolation can // be used. int itnum = 1; // Iteration number int baditer = 1; // Num bisections to take if an iteration is bad. T c, e; // e is our fourth best guess R fe; whileloop: while (!done && (b != nextUp(a)) && !tolerance(a, b)) { T a0 = a, b0 = b; // record the brackets // Do two higher-order (cubic or parabolic) interpolation steps. foreach (int QQ; 0 .. 2) { // Cubic inverse interpolation requires that // all four function values fa, fb, fd, and fe are distinct; // otherwise use quadratic interpolation. bool distinct = (fa != fb) && (fa != fd) && (fa != fe) && (fb != fd) && (fb != fe) && (fd != fe); // The first time, cubic interpolation is impossible. if (itnum<2) distinct = false; bool ok = distinct; if (distinct) { // Cubic inverse interpolation of f(x) at a, b, d, and e immutable q11 = (d - e) * fd / (fe - fd); immutable q21 = (b - d) * fb / (fd - fb); immutable q31 = (a - b) * fa / (fb - fa); immutable d21 = (b - d) * fd / (fd - fb); immutable d31 = (a - b) * fb / (fb - fa); immutable q22 = (d21 - q11) * fb / (fe - fb); immutable q32 = (d31 - q21) * fa / (fd - fa); immutable d32 = (d31 - q21) * fd / (fd - fa); immutable q33 = (d32 - q22) * fa / (fe - fa); c = a + (q31 + q32 + q33); if (c.isNaN() || (c <= a) || (c >= b)) { // DAC: If the interpolation predicts a or b, it's // probable that it's the actual root. Only allow this if // we're already close to the root. if (c == a && a - b != a) { c = nextUp(a); } else if (c == b && a - b != -b) { c = nextDown(b); } else { ok = false; } } } if (!ok) { // DAC: Alefeld doesn't explain why the number of newton steps // should vary. c = newtonQuadratic(distinct ? 3 : 2); if (c.isNaN() || (c <= a) || (c >= b)) { // Failure, try a secant step: c = secant_interpolate(a, b, fa, fb); } } ++itnum; e = d; fe = fd; bracket(c); if (done || ( b == nextUp(a)) || tolerance(a, b)) break whileloop; if (itnum == 2) continue whileloop; } // Now we take a double-length secant step: T u; R fu; if (fabs(fa) < fabs(fb)) { u = a; fu = fa; } else { u = b; fu = fb; } c = u - 2 * (fu / (fb - fa)) * (b - a); // DAC: If the secant predicts a value equal to an endpoint, it's // probably false. if (c == a || c == b || c.isNaN() || fabs(c - u) > (b - a) / 2) { if ((a-b) == a || (b-a) == b) { if ((a>0 && b<0) || (a<0 && b>0)) c = 0; else { if (a == 0) c = ieeeMean(copysign(T(0), b), b); else if (b == 0) c = ieeeMean(copysign(T(0), a), a); else c = ieeeMean(a, b); } } else { c = a + (b - a) / 2; } } e = d; fe = fd; bracket(c); if (done || (b == nextUp(a)) || tolerance(a, b)) break; // IMPROVE THE WORST-CASE PERFORMANCE // We must ensure that the bounds reduce by a factor of 2 // in binary space! every iteration. If we haven't achieved this // yet, or if we don't yet know what the exponent is, // perform a binary chop. if ((a == 0 || b == 0 || (fabs(a) >= T(0.5) * fabs(b) && fabs(b) >= T(0.5) * fabs(a))) && (b - a) < T(0.25) * (b0 - a0)) { baditer = 1; continue; } // DAC: If this happens on consecutive iterations, we probably have a // pathological function. Perform a number of bisections equal to the // total number of consecutive bad iterations. if ((b - a) < T(0.25) * (b0 - a0)) baditer = 1; foreach (int QQ; 0 .. baditer) { e = d; fe = fd; T w; if ((a>0 && b<0) || (a<0 && b>0)) w = 0; else { T usea = a; T useb = b; if (a == 0) usea = copysign(T(0), b); else if (b == 0) useb = copysign(T(0), a); w = ieeeMean(usea, useb); } bracket(w); } ++baditer; } return Tuple!(T, T, R, R)(a, b, fa, fb); } ///ditto Tuple!(T, T, R, R) findRoot(T, R, DF)(scope DF f, const T ax, const T bx, const R fax, const R fbx) { return findRoot(f, ax, bx, fax, fbx, (T a, T b) => false); } ///ditto T findRoot(T, R)(scope R delegate(T) f, const T a, const T b, scope bool delegate(T lo, T hi) tolerance = (T a, T b) => false) { return findRoot!(T, R delegate(T), bool delegate(T lo, T hi))(f, a, b, tolerance); } @safe nothrow unittest { int numProblems = 0; int numCalls; void testFindRoot(real delegate(real) @nogc @safe nothrow pure f , real x1, real x2) @nogc @safe nothrow pure { //numCalls=0; //++numProblems; assert(!x1.isNaN() && !x2.isNaN()); assert(signbit(f(x1)) != signbit(f(x2))); auto result = findRoot(f, x1, x2, f(x1), f(x2), (real lo, real hi) { return false; }); auto flo = f(result[0]); auto fhi = f(result[1]); if (flo != 0) { assert(oppositeSigns(flo, fhi)); } } // Test functions real cubicfn(real x) @nogc @safe nothrow pure { //++numCalls; if (x>float.max) x = float.max; if (x<-float.max) x = -float.max; // This has a single real root at -59.286543284815 return 0.386*x*x*x + 23*x*x + 15.7*x + 525.2; } // Test a function with more than one root. real multisine(real x) { ++numCalls; return sin(x); } testFindRoot( &multisine, 6, 90); testFindRoot(&cubicfn, -100, 100); testFindRoot( &cubicfn, -double.max, real.max); /* Tests from the paper: * "On Enclosing Simple Roots of Nonlinear Equations", G. Alefeld, F.A. Potra, * Yixun Shi, Mathematics of Computation 61, pp733-744 (1993). */ // Parameters common to many alefeld tests. int n; real ale_a, ale_b; int powercalls = 0; real power(real x) { ++powercalls; ++numCalls; return pow(x, n) + double.min_normal; } int [] power_nvals = [3, 5, 7, 9, 19, 25]; // Alefeld paper states that pow(x,n) is a very poor case, where bisection // outperforms his method, and gives total numcalls = // 921 for bisection (2.4 calls per bit), 1830 for Alefeld (4.76/bit), // 2624 for brent (6.8/bit) // ... but that is for double, not real80. // This poor performance seems mainly due to catastrophic cancellation, // which is avoided here by the use of ieeeMean(). // I get: 231 (0.48/bit). // IE this is 10X faster in Alefeld's worst case numProblems=0; foreach (k; power_nvals) { n = k; testFindRoot(&power, -1, 10); } int powerProblems = numProblems; // Tests from Alefeld paper int [9] alefeldSums; real alefeld0(real x) { ++alefeldSums[0]; ++numCalls; real q = sin(x) - x/2; for (int i=1; i<20; ++i) q+=(2*i-5.0)*(2*i-5.0)/((x-i*i)*(x-i*i)*(x-i*i)); return q; } real alefeld1(real x) { ++numCalls; ++alefeldSums[1]; return ale_a*x + exp(ale_b * x); } real alefeld2(real x) { ++numCalls; ++alefeldSums[2]; return pow(x, n) - ale_a; } real alefeld3(real x) { ++numCalls; ++alefeldSums[3]; return (1.0 +pow(1.0L-n, 2))*x - pow(1.0L-n*x, 2); } real alefeld4(real x) { ++numCalls; ++alefeldSums[4]; return x*x - pow(1-x, n); } real alefeld5(real x) { ++numCalls; ++alefeldSums[5]; return (1+pow(1.0L-n, 4))*x - pow(1.0L-n*x, 4); } real alefeld6(real x) { ++numCalls; ++alefeldSums[6]; return exp(-n*x)*(x-1.01L) + pow(x, n); } real alefeld7(real x) { ++numCalls; ++alefeldSums[7]; return (n*x-1)/((n-1)*x); } numProblems=0; testFindRoot(&alefeld0, PI_2, PI); for (n=1; n <= 10; ++n) { testFindRoot(&alefeld0, n*n+1e-9L, (n+1)*(n+1)-1e-9L); } ale_a = -40; ale_b = -1; testFindRoot(&alefeld1, -9, 31); ale_a = -100; ale_b = -2; testFindRoot(&alefeld1, -9, 31); ale_a = -200; ale_b = -3; testFindRoot(&alefeld1, -9, 31); int [] nvals_3 = [1, 2, 5, 10, 15, 20]; int [] nvals_5 = [1, 2, 4, 5, 8, 15, 20]; int [] nvals_6 = [1, 5, 10, 15, 20]; int [] nvals_7 = [2, 5, 15, 20]; for (int i=4; i<12; i+=2) { n = i; ale_a = 0.2; testFindRoot(&alefeld2, 0, 5); ale_a=1; testFindRoot(&alefeld2, 0.95, 4.05); testFindRoot(&alefeld2, 0, 1.5); } foreach (i; nvals_3) { n=i; testFindRoot(&alefeld3, 0, 1); } foreach (i; nvals_3) { n=i; testFindRoot(&alefeld4, 0, 1); } foreach (i; nvals_5) { n=i; testFindRoot(&alefeld5, 0, 1); } foreach (i; nvals_6) { n=i; testFindRoot(&alefeld6, 0, 1); } foreach (i; nvals_7) { n=i; testFindRoot(&alefeld7, 0.01L, 1); } real worstcase(real x) { ++numCalls; return x<0.3*real.max? -0.999e-3 : 1.0; } testFindRoot(&worstcase, -real.max, real.max); // just check that the double + float cases compile findRoot((double x){ return 0.0; }, -double.max, double.max); findRoot((float x){ return 0.0f; }, -float.max, float.max); /* int grandtotal=0; foreach (calls; alefeldSums) { grandtotal+=calls; } grandtotal-=2*numProblems; printf("\nALEFELD TOTAL = %d avg = %f (alefeld avg=19.3 for double)\n", grandtotal, (1.0*grandtotal)/numProblems); powercalls -= 2*powerProblems; printf("POWER TOTAL = %d avg = %f ", powercalls, (1.0*powercalls)/powerProblems); */ // https://issues.dlang.org/show_bug.cgi?id=14231 auto xp = findRoot((float x) => x, 0f, 1f); auto xn = findRoot((float x) => x, -1f, -0f); } //regression control @system unittest { // @system due to the case in the 2nd line static assert(__traits(compiles, findRoot((float x)=>cast(real) x, float.init, float.init))); static assert(__traits(compiles, findRoot!real((x)=>cast(double) x, real.init, real.init))); static assert(__traits(compiles, findRoot((real x)=>cast(double) x, real.init, real.init))); } /++ Find a real minimum of a real function `f(x)` via bracketing. Given a function `f` and a range `(ax .. bx)`, returns the value of `x` in the range which is closest to a minimum of `f(x)`. `f` is never evaluted at the endpoints of `ax` and `bx`. If `f(x)` has more than one minimum in the range, one will be chosen arbitrarily. If `f(x)` returns NaN or -Infinity, `(x, f(x), NaN)` will be returned; otherwise, this algorithm is guaranteed to succeed. Params: f = Function to be analyzed ax = Left bound of initial range of f known to contain the minimum. bx = Right bound of initial range of f known to contain the minimum. relTolerance = Relative tolerance. absTolerance = Absolute tolerance. Preconditions: `ax` and `bx` shall be finite reals. $(BR) `relTolerance` shall be normal positive real. $(BR) `absTolerance` shall be normal positive real no less then `T.epsilon*2`. Returns: A tuple consisting of `x`, `y = f(x)` and `error = 3 * (absTolerance * fabs(x) + relTolerance)`. The method used is a combination of golden section search and successive parabolic interpolation. Convergence is never much slower than that for a Fibonacci search. References: "Algorithms for Minimization without Derivatives", Richard Brent, Prentice-Hall, Inc. (1973) See_Also: $(LREF findRoot), $(REF isNormal, std,math) +/ Tuple!(T, "x", Unqual!(ReturnType!DF), "y", T, "error") findLocalMin(T, DF)( scope DF f, const T ax, const T bx, const T relTolerance = sqrt(T.epsilon), const T absTolerance = sqrt(T.epsilon), ) if (isFloatingPoint!T && __traits(compiles, {T _ = DF.init(T.init);})) in { assert(isFinite(ax), "ax is not finite"); assert(isFinite(bx), "bx is not finite"); assert(isNormal(relTolerance), "relTolerance is not normal floating point number"); assert(isNormal(absTolerance), "absTolerance is not normal floating point number"); assert(relTolerance >= 0, "absTolerance is not positive"); assert(absTolerance >= T.epsilon*2, "absTolerance is not greater then `2*T.epsilon`"); } out (result) { assert(isFinite(result.x)); } do { alias R = Unqual!(CommonType!(ReturnType!DF, T)); // c is the squared inverse of the golden ratio // (3 - sqrt(5))/2 // Value obtained from Wolfram Alpha. enum T c = 0x0.61c8864680b583ea0c633f9fa31237p+0L; enum T cm1 = 0x0.9e3779b97f4a7c15f39cc0605cedc8p+0L; R tolerance; T a = ax > bx ? bx : ax; T b = ax > bx ? ax : bx; // sequence of declarations suitable for SIMD instructions T v = a * cm1 + b * c; assert(isFinite(v)); R fv = f(v); if (isNaN(fv) || fv == -T.infinity) { return typeof(return)(v, fv, T.init); } T w = v; R fw = fv; T x = v; R fx = fv; size_t i; for (R d = 0, e = 0;;) { i++; T m = (a + b) / 2; // This fix is not part of the original algorithm if (!isFinite(m)) // fix infinity loop. Issue can be reproduced in R. { m = a / 2 + b / 2; if (!isFinite(m)) // fast-math compiler switch is enabled { //SIMD instructions can be used by compiler, do not reduce declarations int a_exp = void; int b_exp = void; immutable an = frexp(a, a_exp); immutable bn = frexp(b, b_exp); immutable am = ldexp(an, a_exp-1); immutable bm = ldexp(bn, b_exp-1); m = am + bm; if (!isFinite(m)) // wrong input: constraints are disabled in release mode { return typeof(return).init; } } } tolerance = absTolerance * fabs(x) + relTolerance; immutable t2 = tolerance * 2; // check stopping criterion if (!(fabs(x - m) > t2 - (b - a) / 2)) { break; } R p = 0; R q = 0; R r = 0; // fit parabola if (fabs(e) > tolerance) { immutable xw = x - w; immutable fxw = fx - fw; immutable xv = x - v; immutable fxv = fx - fv; immutable xwfxv = xw * fxv; immutable xvfxw = xv * fxw; p = xv * xvfxw - xw * xwfxv; q = (xvfxw - xwfxv) * 2; if (q > 0) p = -p; else q = -q; r = e; e = d; } T u; // a parabolic-interpolation step if (fabs(p) < fabs(q * r / 2) && p > q * (a - x) && p < q * (b - x)) { d = p / q; u = x + d; // f must not be evaluated too close to a or b if (u - a < t2 || b - u < t2) d = x < m ? tolerance : -tolerance; } // a golden-section step else { e = (x < m ? b : a) - x; d = c * e; } // f must not be evaluated too close to x u = x + (fabs(d) >= tolerance ? d : d > 0 ? tolerance : -tolerance); immutable fu = f(u); if (isNaN(fu) || fu == -T.infinity) { return typeof(return)(u, fu, T.init); } // update a, b, v, w, and x if (fu <= fx) { (u < x ? b : a) = x; v = w; fv = fw; w = x; fw = fx; x = u; fx = fu; } else { (u < x ? a : b) = u; if (fu <= fw || w == x) { v = w; fv = fw; w = u; fw = fu; } else if (fu <= fv || v == x || v == w) { // do not remove this braces v = u; fv = fu; } } } return typeof(return)(x, fx, tolerance * 3); } /// @safe unittest { import std.math.operations : isClose; auto ret = findLocalMin((double x) => (x-4)^^2, -1e7, 1e7); assert(ret.x.isClose(4.0)); assert(ret.y.isClose(0.0, 0.0, 1e-10)); } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(double, float, real)) { { auto ret = findLocalMin!T((T x) => (x-4)^^2, T.min_normal, 1e7); assert(ret.x.isClose(T(4))); assert(ret.y.isClose(T(0), 0.0, T.epsilon)); } { auto ret = findLocalMin!T((T x) => fabs(x-1), -T.max/4, T.max/4, T.min_normal, 2*T.epsilon); assert(isClose(ret.x, T(1))); assert(isClose(ret.y, T(0), 0.0, T.epsilon)); assert(ret.error <= 10 * T.epsilon); } { auto ret = findLocalMin!T((T x) => T.init, 0, 1, T.min_normal, 2*T.epsilon); assert(!ret.x.isNaN); assert(ret.y.isNaN); assert(ret.error.isNaN); } { auto ret = findLocalMin!T((T x) => log(x), 0, 1, T.min_normal, 2*T.epsilon); assert(ret.error < 3.00001 * ((2*T.epsilon)*fabs(ret.x)+ T.min_normal)); assert(ret.x >= 0 && ret.x <= ret.error); } { auto ret = findLocalMin!T((T x) => log(x), 0, T.max, T.min_normal, 2*T.epsilon); assert(ret.y < -18); assert(ret.error < 5e-08); assert(ret.x >= 0 && ret.x <= ret.error); } { auto ret = findLocalMin!T((T x) => -fabs(x), -1, 1, T.min_normal, 2*T.epsilon); assert(ret.x.fabs.isClose(T(1))); assert(ret.y.fabs.isClose(T(1))); assert(ret.error.isClose(T(0), 0.0, 100*T.epsilon)); } } } /** Computes $(LINK2 https://en.wikipedia.org/wiki/Euclidean_distance, Euclidean distance) between input ranges `a` and `b`. The two ranges must have the same length. The three-parameter version stops computation as soon as the distance is greater than or equal to `limit` (this is useful to save computation if a small distance is sought). */ CommonType!(ElementType!(Range1), ElementType!(Range2)) euclideanDistance(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!(Range1) && isInputRange!(Range2)) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t = a.front - b.front; result += t * t; } static if (!haveLen) assert(b.empty); return sqrt(result); } /// Ditto CommonType!(ElementType!(Range1), ElementType!(Range2)) euclideanDistance(Range1, Range2, F)(Range1 a, Range2 b, F limit) if (isInputRange!(Range1) && isInputRange!(Range2)) { limit *= limit; enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; for (; ; a.popFront(), b.popFront()) { if (a.empty) { static if (!haveLen) assert(b.empty); break; } immutable t = a.front - b.front; result += t * t; if (result >= limit) break; } return sqrt(result); } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(double, const double, immutable double)) {{ T[] a = [ 1.0, 2.0, ]; T[] b = [ 4.0, 6.0, ]; assert(euclideanDistance(a, b) == 5); assert(euclideanDistance(a, b, 6) == 5); assert(euclideanDistance(a, b, 5) == 5); assert(euclideanDistance(a, b, 4) == 5); assert(euclideanDistance(a, b, 2) == 3); }} } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Dot_product, dot product) of input ranges `a` and $(D b). The two ranges must have the same length. If both ranges define length, the check is done once; otherwise, it is done at each iteration. */ CommonType!(ElementType!(Range1), ElementType!(Range2)) dotProduct(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!(Range1) && isInputRange!(Range2) && !(isArray!(Range1) && isArray!(Range2))) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { result += a.front * b.front; } static if (!haveLen) assert(b.empty); return result; } /// Ditto CommonType!(F1, F2) dotProduct(F1, F2)(in F1[] avector, in F2[] bvector) { immutable n = avector.length; assert(n == bvector.length); auto avec = avector.ptr, bvec = bvector.ptr; Unqual!(typeof(return)) sum0 = 0, sum1 = 0; const all_endp = avec + n; const smallblock_endp = avec + (n & ~3); const bigblock_endp = avec + (n & ~15); for (; avec != bigblock_endp; avec += 16, bvec += 16) { sum0 += avec[0] * bvec[0]; sum1 += avec[1] * bvec[1]; sum0 += avec[2] * bvec[2]; sum1 += avec[3] * bvec[3]; sum0 += avec[4] * bvec[4]; sum1 += avec[5] * bvec[5]; sum0 += avec[6] * bvec[6]; sum1 += avec[7] * bvec[7]; sum0 += avec[8] * bvec[8]; sum1 += avec[9] * bvec[9]; sum0 += avec[10] * bvec[10]; sum1 += avec[11] * bvec[11]; sum0 += avec[12] * bvec[12]; sum1 += avec[13] * bvec[13]; sum0 += avec[14] * bvec[14]; sum1 += avec[15] * bvec[15]; } for (; avec != smallblock_endp; avec += 4, bvec += 4) { sum0 += avec[0] * bvec[0]; sum1 += avec[1] * bvec[1]; sum0 += avec[2] * bvec[2]; sum1 += avec[3] * bvec[3]; } sum0 += sum1; /* Do trailing portion in naive loop. */ while (avec != all_endp) { sum0 += *avec * *bvec; ++avec; ++bvec; } return sum0; } /// ditto F dotProduct(F, uint N)(const ref scope F[N] a, const ref scope F[N] b) if (N <= 16) { F sum0 = 0; F sum1 = 0; static foreach (i; 0 .. N / 2) { sum0 += a[i*2] * b[i*2]; sum1 += a[i*2+1] * b[i*2+1]; } static if (N % 2 == 1) { sum0 += a[N-1] * b[N-1]; } return sum0 + sum1; } @system unittest { // @system due to dotProduct and assertCTFEable import std.exception : assertCTFEable; import std.meta : AliasSeq; static foreach (T; AliasSeq!(double, const double, immutable double)) {{ T[] a = [ 1.0, 2.0, ]; T[] b = [ 4.0, 6.0, ]; assert(dotProduct(a, b) == 16); assert(dotProduct([1, 3, -5], [4, -2, -1]) == 3); // Test with fixed-length arrays. T[2] c = [ 1.0, 2.0, ]; T[2] d = [ 4.0, 6.0, ]; assert(dotProduct(c, d) == 16); T[3] e = [1, 3, -5]; T[3] f = [4, -2, -1]; assert(dotProduct(e, f) == 3); }} // Make sure the unrolled loop codepath gets tested. static const x = [1.0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]; static const y = [2.0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]; assertCTFEable!({ assert(dotProduct(x, y) == 4048); }); } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Cosine_similarity, cosine similarity) of input ranges `a` and $(D b). The two ranges must have the same length. If both ranges define length, the check is done once; otherwise, it is done at each iteration. If either range has all-zero elements, return 0. */ CommonType!(ElementType!(Range1), ElementType!(Range2)) cosineSimilarity(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!(Range1) && isInputRange!(Range2)) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) norma = 0, normb = 0, dotprod = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front, t2 = b.front; norma += t1 * t1; normb += t2 * t2; dotprod += t1 * t2; } static if (!haveLen) assert(b.empty); if (norma == 0 || normb == 0) return 0; return dotprod / sqrt(norma * normb); } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(double, const double, immutable double)) {{ T[] a = [ 1.0, 2.0, ]; T[] b = [ 4.0, 3.0, ]; assert(isClose( cosineSimilarity(a, b), 10.0 / sqrt(5.0 * 25), 0.01)); }} } /** Normalizes values in `range` by multiplying each element with a number chosen such that values sum up to `sum`. If elements in $(D range) sum to zero, assigns $(D sum / range.length) to all. Normalization makes sense only if all elements in `range` are positive. `normalize` assumes that is the case without checking it. Returns: `true` if normalization completed normally, `false` if all elements in `range` were zero or if `range` is empty. */ bool normalize(R)(R range, ElementType!(R) sum = 1) if (isForwardRange!(R)) { ElementType!(R) s = 0; // Step 1: Compute sum and length of the range static if (hasLength!(R)) { const length = range.length; foreach (e; range) { s += e; } } else { uint length = 0; foreach (e; range) { s += e; ++length; } } // Step 2: perform normalization if (s == 0) { if (length) { immutable f = sum / range.length; foreach (ref e; range) e = f; } return false; } // The path most traveled assert(s >= 0); immutable f = sum / s; foreach (ref e; range) e *= f; return true; } /// @safe unittest { double[] a = []; assert(!normalize(a)); a = [ 1.0, 3.0 ]; assert(normalize(a)); assert(a == [ 0.25, 0.75 ]); assert(normalize!(typeof(a))(a, 50)); // a = [12.5, 37.5] a = [ 0.0, 0.0 ]; assert(!normalize(a)); assert(a == [ 0.5, 0.5 ]); } /** Compute the sum of binary logarithms of the input range `r`. The error of this method is much smaller than with a naive sum of log2. */ ElementType!Range sumOfLog2s(Range)(Range r) if (isInputRange!Range && isFloatingPoint!(ElementType!Range)) { long exp = 0; Unqual!(typeof(return)) x = 1; foreach (e; r) { if (e < 0) return typeof(return).nan; int lexp = void; x *= frexp(e, lexp); exp += lexp; if (x < 0.5) { x *= 2; exp--; } } return exp + log2(x); } /// @safe unittest { import std.math.traits : isNaN; assert(sumOfLog2s(new double[0]) == 0); assert(sumOfLog2s([0.0L]) == -real.infinity); assert(sumOfLog2s([-0.0L]) == -real.infinity); assert(sumOfLog2s([2.0L]) == 1); assert(sumOfLog2s([-2.0L]).isNaN()); assert(sumOfLog2s([real.nan]).isNaN()); assert(sumOfLog2s([-real.nan]).isNaN()); assert(sumOfLog2s([real.infinity]) == real.infinity); assert(sumOfLog2s([-real.infinity]).isNaN()); assert(sumOfLog2s([ 0.25, 0.25, 0.25, 0.125 ]) == -9); } /** Computes $(LINK2 https://en.wikipedia.org/wiki/Entropy_(information_theory), _entropy) of input range `r` in bits. This function assumes (without checking) that the values in `r` are all in $(D [0, 1]). For the entropy to be meaningful, often `r` should be normalized too (i.e., its values should sum to 1). The two-parameter version stops evaluating as soon as the intermediate result is greater than or equal to `max`. */ ElementType!Range entropy(Range)(Range r) if (isInputRange!Range) { Unqual!(typeof(return)) result = 0.0; for (;!r.empty; r.popFront) { if (!r.front) continue; result -= r.front * log2(r.front); } return result; } /// Ditto ElementType!Range entropy(Range, F)(Range r, F max) if (isInputRange!Range && !is(CommonType!(ElementType!Range, F) == void)) { Unqual!(typeof(return)) result = 0.0; for (;!r.empty; r.popFront) { if (!r.front) continue; result -= r.front * log2(r.front); if (result >= max) break; } return result; } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(double, const double, immutable double)) {{ T[] p = [ 0.0, 0, 0, 1 ]; assert(entropy(p) == 0); p = [ 0.25, 0.25, 0.25, 0.25 ]; assert(entropy(p) == 2); assert(entropy(p, 1) == 1); }} } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence, Kullback-Leibler divergence) between input ranges `a` and `b`, which is the sum $(D ai * log(ai / bi)). The base of logarithm is 2. The ranges are assumed to contain elements in $(D [0, 1]). Usually the ranges are normalized probability distributions, but this is not required or checked by $(D kullbackLeiblerDivergence). If any element `bi` is zero and the corresponding element `ai` nonzero, returns infinity. (Otherwise, if $(D ai == 0 && bi == 0), the term $(D ai * log(ai / bi)) is considered zero.) If the inputs are normalized, the result is positive. */ CommonType!(ElementType!Range1, ElementType!Range2) kullbackLeiblerDivergence(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!(Range1) && isInputRange!(Range2)) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front; if (t1 == 0) continue; immutable t2 = b.front; if (t2 == 0) return result.infinity; assert(t1 > 0 && t2 > 0); result += t1 * log2(t1 / t2); } static if (!haveLen) assert(b.empty); return result; } /// @safe unittest { import std.math.operations : isClose; double[] p = [ 0.0, 0, 0, 1 ]; assert(kullbackLeiblerDivergence(p, p) == 0); double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ]; assert(kullbackLeiblerDivergence(p1, p1) == 0); assert(kullbackLeiblerDivergence(p, p1) == 2); assert(kullbackLeiblerDivergence(p1, p) == double.infinity); double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ]; assert(isClose(kullbackLeiblerDivergence(p1, p2), 0.0719281, 1e-5)); assert(isClose(kullbackLeiblerDivergence(p2, p1), 0.0780719, 1e-5)); } /** Computes the $(LINK2 https://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence, Jensen-Shannon divergence) between `a` and $(D b), which is the sum $(D (ai * log(2 * ai / (ai + bi)) + bi * log(2 * bi / (ai + bi))) / 2). The base of logarithm is 2. The ranges are assumed to contain elements in $(D [0, 1]). Usually the ranges are normalized probability distributions, but this is not required or checked by `jensenShannonDivergence`. If the inputs are normalized, the result is bounded within $(D [0, 1]). The three-parameter version stops evaluations as soon as the intermediate result is greater than or equal to `limit`. */ CommonType!(ElementType!Range1, ElementType!Range2) jensenShannonDivergence(Range1, Range2)(Range1 a, Range2 b) if (isInputRange!Range1 && isInputRange!Range2 && is(CommonType!(ElementType!Range1, ElementType!Range2))) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front; immutable t2 = b.front; immutable avg = (t1 + t2) / 2; if (t1 != 0) { result += t1 * log2(t1 / avg); } if (t2 != 0) { result += t2 * log2(t2 / avg); } } static if (!haveLen) assert(b.empty); return result / 2; } /// Ditto CommonType!(ElementType!Range1, ElementType!Range2) jensenShannonDivergence(Range1, Range2, F)(Range1 a, Range2 b, F limit) if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(CommonType!(ElementType!Range1, ElementType!Range2).init >= F.init) : bool)) { enum bool haveLen = hasLength!(Range1) && hasLength!(Range2); static if (haveLen) assert(a.length == b.length); Unqual!(typeof(return)) result = 0; limit *= 2; for (; !a.empty; a.popFront(), b.popFront()) { immutable t1 = a.front; immutable t2 = b.front; immutable avg = (t1 + t2) / 2; if (t1 != 0) { result += t1 * log2(t1 / avg); } if (t2 != 0) { result += t2 * log2(t2 / avg); } if (result >= limit) break; } static if (!haveLen) assert(b.empty); return result / 2; } /// @safe unittest { import std.math.operations : isClose; double[] p = [ 0.0, 0, 0, 1 ]; assert(jensenShannonDivergence(p, p) == 0); double[] p1 = [ 0.25, 0.25, 0.25, 0.25 ]; assert(jensenShannonDivergence(p1, p1) == 0); assert(isClose(jensenShannonDivergence(p1, p), 0.548795, 1e-5)); double[] p2 = [ 0.2, 0.2, 0.2, 0.4 ]; assert(isClose(jensenShannonDivergence(p1, p2), 0.0186218, 1e-5)); assert(isClose(jensenShannonDivergence(p2, p1), 0.0186218, 1e-5)); assert(isClose(jensenShannonDivergence(p2, p1, 0.005), 0.00602366, 1e-5)); } /** The so-called "all-lengths gap-weighted string kernel" computes a similarity measure between `s` and `t` based on all of their common subsequences of all lengths. Gapped subsequences are also included. To understand what $(D gapWeightedSimilarity(s, t, lambda)) computes, consider first the case $(D lambda = 1) and the strings $(D s = ["Hello", "brave", "new", "world"]) and $(D t = ["Hello", "new", "world"]). In that case, `gapWeightedSimilarity` counts the following matches: $(OL $(LI three matches of length 1, namely `"Hello"`, `"new"`, and `"world"`;) $(LI three matches of length 2, namely ($(D "Hello", "new")), ($(D "Hello", "world")), and ($(D "new", "world"));) $(LI one match of length 3, namely ($(D "Hello", "new", "world")).)) The call $(D gapWeightedSimilarity(s, t, 1)) simply counts all of these matches and adds them up, returning 7. ---- string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; assert(gapWeightedSimilarity(s, t, 1) == 7); ---- Note how the gaps in matching are simply ignored, for example ($(D "Hello", "new")) is deemed as good a match as ($(D "new", "world")). This may be too permissive for some applications. To eliminate gapped matches entirely, use $(D lambda = 0): ---- string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; assert(gapWeightedSimilarity(s, t, 0) == 4); ---- The call above eliminated the gapped matches ($(D "Hello", "new")), ($(D "Hello", "world")), and ($(D "Hello", "new", "world")) from the tally. That leaves only 4 matches. The most interesting case is when gapped matches still participate in the result, but not as strongly as ungapped matches. The result will be a smooth, fine-grained similarity measure between the input strings. This is where values of `lambda` between 0 and 1 enter into play: gapped matches are $(I exponentially penalized with the number of gaps) with base `lambda`. This means that an ungapped match adds 1 to the return value; a match with one gap in either string adds `lambda` to the return value; ...; a match with a total of `n` gaps in both strings adds $(D pow(lambda, n)) to the return value. In the example above, we have 4 matches without gaps, 2 matches with one gap, and 1 match with three gaps. The latter match is ($(D "Hello", "world")), which has two gaps in the first string and one gap in the second string, totaling to three gaps. Summing these up we get $(D 4 + 2 * lambda + pow(lambda, 3)). ---- string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 0.5 * 2 + 0.125); ---- `gapWeightedSimilarity` is useful wherever a smooth similarity measure between sequences allowing for approximate matches is needed. The examples above are given with words, but any sequences with elements comparable for equality are allowed, e.g. characters or numbers. `gapWeightedSimilarity` uses a highly optimized dynamic programming implementation that needs $(D 16 * min(s.length, t.length)) extra bytes of memory and $(BIGOH s.length * t.length) time to complete. */ F gapWeightedSimilarity(alias comp = "a == b", R1, R2, F)(R1 s, R2 t, F lambda) if (isRandomAccessRange!(R1) && hasLength!(R1) && isRandomAccessRange!(R2) && hasLength!(R2)) { import core.exception : onOutOfMemoryError; import core.stdc.stdlib : malloc, free; import std.algorithm.mutation : swap; import std.functional : binaryFun; if (s.length < t.length) return gapWeightedSimilarity(t, s, lambda); if (!t.length) return 0; auto dpvi = cast(F*) malloc(F.sizeof * 2 * t.length); if (!dpvi) onOutOfMemoryError(); auto dpvi1 = dpvi + t.length; scope(exit) free(dpvi < dpvi1 ? dpvi : dpvi1); dpvi[0 .. t.length] = 0; dpvi1[0] = 0; immutable lambda2 = lambda * lambda; F result = 0; foreach (i; 0 .. s.length) { const si = s[i]; for (size_t j = 0;;) { F dpsij = void; if (binaryFun!(comp)(si, t[j])) { dpsij = 1 + dpvi[j]; result += dpsij; } else { dpsij = 0; } immutable j1 = j + 1; if (j1 == t.length) break; dpvi1[j1] = dpsij + lambda * (dpvi1[j] + dpvi[j1]) - lambda2 * dpvi[j]; j = j1; } swap(dpvi, dpvi1); } return result; } @system unittest { string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; assert(gapWeightedSimilarity(s, t, 1) == 7); assert(gapWeightedSimilarity(s, t, 0) == 4); assert(gapWeightedSimilarity(s, t, 0.5) == 4 + 2 * 0.5 + 0.125); } /** The similarity per `gapWeightedSimilarity` has an issue in that it grows with the lengths of the two strings, even though the strings are not actually very similar. For example, the range $(D ["Hello", "world"]) is increasingly similar with the range $(D ["Hello", "world", "world", "world",...]) as more instances of `"world"` are appended. To prevent that, `gapWeightedSimilarityNormalized` computes a normalized version of the similarity that is computed as $(D gapWeightedSimilarity(s, t, lambda) / sqrt(gapWeightedSimilarity(s, t, lambda) * gapWeightedSimilarity(s, t, lambda))). The function `gapWeightedSimilarityNormalized` (a so-called normalized kernel) is bounded in $(D [0, 1]), reaches `0` only for ranges that don't match in any position, and `1` only for identical ranges. The optional parameters `sSelfSim` and `tSelfSim` are meant for avoiding duplicate computation. Many applications may have already computed $(D gapWeightedSimilarity(s, s, lambda)) and/or $(D gapWeightedSimilarity(t, t, lambda)). In that case, they can be passed as `sSelfSim` and `tSelfSim`, respectively. */ Select!(isFloatingPoint!(F), F, double) gapWeightedSimilarityNormalized(alias comp = "a == b", R1, R2, F) (R1 s, R2 t, F lambda, F sSelfSim = F.init, F tSelfSim = F.init) if (isRandomAccessRange!(R1) && hasLength!(R1) && isRandomAccessRange!(R2) && hasLength!(R2)) { static bool uncomputed(F n) { static if (isFloatingPoint!(F)) return isNaN(n); else return n == n.init; } if (uncomputed(sSelfSim)) sSelfSim = gapWeightedSimilarity!(comp)(s, s, lambda); if (sSelfSim == 0) return 0; if (uncomputed(tSelfSim)) tSelfSim = gapWeightedSimilarity!(comp)(t, t, lambda); if (tSelfSim == 0) return 0; return gapWeightedSimilarity!(comp)(s, t, lambda) / sqrt(cast(typeof(return)) sSelfSim * tSelfSim); } /// @system unittest { import std.math.operations : isClose; import std.math.algebraic : sqrt; string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; assert(gapWeightedSimilarity(s, s, 1) == 15); assert(gapWeightedSimilarity(t, t, 1) == 7); assert(gapWeightedSimilarity(s, t, 1) == 7); assert(isClose(gapWeightedSimilarityNormalized(s, t, 1), 7.0 / sqrt(15.0 * 7), 0.01)); } /** Similar to `gapWeightedSimilarity`, just works in an incremental manner by first revealing the matches of length 1, then gapped matches of length 2, and so on. The memory requirement is $(BIGOH s.length * t.length). The time complexity is $(BIGOH s.length * t.length) time for computing each step. Continuing on the previous example: The implementation is based on the pseudocode in Fig. 4 of the paper $(HTTP jmlr.csail.mit.edu/papers/volume6/rousu05a/rousu05a.pdf, "Efficient Computation of Gapped Substring Kernels on Large Alphabets") by Rousu et al., with additional algorithmic and systems-level optimizations. */ struct GapWeightedSimilarityIncremental(Range, F = double) if (isRandomAccessRange!(Range) && hasLength!(Range)) { import core.stdc.stdlib : malloc, realloc, alloca, free; private: Range s, t; F currentValue = 0; F* kl; size_t gram = void; F lambda = void, lambda2 = void; public: /** Constructs an object given two ranges `s` and `t` and a penalty `lambda`. Constructor completes in $(BIGOH s.length * t.length) time and computes all matches of length 1. */ this(Range s, Range t, F lambda) { import core.exception : onOutOfMemoryError; assert(lambda > 0); this.gram = 0; this.lambda = lambda; this.lambda2 = lambda * lambda; // for efficiency only size_t iMin = size_t.max, jMin = size_t.max, iMax = 0, jMax = 0; /* initialize */ Tuple!(size_t, size_t) * k0; size_t k0len; scope(exit) free(k0); currentValue = 0; foreach (i, si; s) { foreach (j; 0 .. t.length) { if (si != t[j]) continue; k0 = cast(typeof(k0)) realloc(k0, ++k0len * (*k0).sizeof); with (k0[k0len - 1]) { field[0] = i; field[1] = j; } // Maintain the minimum and maximum i and j if (iMin > i) iMin = i; if (iMax < i) iMax = i; if (jMin > j) jMin = j; if (jMax < j) jMax = j; } } if (iMin > iMax) return; assert(k0len); currentValue = k0len; // Chop strings down to the useful sizes s = s[iMin .. iMax + 1]; t = t[jMin .. jMax + 1]; this.s = s; this.t = t; kl = cast(F*) malloc(s.length * t.length * F.sizeof); if (!kl) onOutOfMemoryError(); kl[0 .. s.length * t.length] = 0; foreach (pos; 0 .. k0len) { with (k0[pos]) { kl[(field[0] - iMin) * t.length + field[1] -jMin] = lambda2; } } } /** Returns: `this`. */ ref GapWeightedSimilarityIncremental opSlice() { return this; } /** Computes the match of the popFront length. Completes in $(BIGOH s.length * t.length) time. */ void popFront() { import std.algorithm.mutation : swap; // This is a large source of optimization: if similarity at // the gram-1 level was 0, then we can safely assume // similarity at the gram level is 0 as well. if (empty) return; // Now attempt to match gapped substrings of length `gram' ++gram; currentValue = 0; auto Si = cast(F*) alloca(t.length * F.sizeof); Si[0 .. t.length] = 0; foreach (i; 0 .. s.length) { const si = s[i]; F Sij_1 = 0; F Si_1j_1 = 0; auto kli = kl + i * t.length; for (size_t j = 0;;) { const klij = kli[j]; const Si_1j = Si[j]; const tmp = klij + lambda * (Si_1j + Sij_1) - lambda2 * Si_1j_1; // now update kl and currentValue if (si == t[j]) currentValue += kli[j] = lambda2 * Si_1j_1; else kli[j] = 0; // commit to Si Si[j] = tmp; if (++j == t.length) break; // get ready for the popFront step; virtually increment j, // so essentially stuffj_1 <-- stuffj Si_1j_1 = Si_1j; Sij_1 = tmp; } } currentValue /= pow(lambda, 2 * (gram + 1)); version (none) { Si_1[0 .. t.length] = 0; kl[0 .. min(t.length, maxPerimeter + 1)] = 0; foreach (i; 1 .. min(s.length, maxPerimeter + 1)) { auto kli = kl + i * t.length; assert(s.length > i); const si = s[i]; auto kl_1i_1 = kl_1 + (i - 1) * t.length; kli[0] = 0; F lastS = 0; foreach (j; 1 .. min(maxPerimeter - i + 1, t.length)) { immutable j_1 = j - 1; immutable tmp = kl_1i_1[j_1] + lambda * (Si_1[j] + lastS) - lambda2 * Si_1[j_1]; kl_1i_1[j_1] = float.nan; Si_1[j_1] = lastS; lastS = tmp; if (si == t[j]) { currentValue += kli[j] = lambda2 * lastS; } else { kli[j] = 0; } } Si_1[t.length - 1] = lastS; } currentValue /= pow(lambda, 2 * (gram + 1)); // get ready for the popFront computation swap(kl, kl_1); } } /** Returns: The gapped similarity at the current match length (initially 1, grows with each call to `popFront`). */ @property F front() { return currentValue; } /** Returns: Whether there are more matches. */ @property bool empty() { if (currentValue) return false; if (kl) { free(kl); kl = null; } return true; } } /** Ditto */ GapWeightedSimilarityIncremental!(R, F) gapWeightedSimilarityIncremental(R, F) (R r1, R r2, F penalty) { return typeof(return)(r1, r2, penalty); } /// @system unittest { string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); assert(simIter.front == 3); // three 1-length matches simIter.popFront(); assert(simIter.front == 3); // three 2-length matches simIter.popFront(); assert(simIter.front == 1); // one 3-length match simIter.popFront(); assert(simIter.empty); // no more match } @system unittest { import std.conv : text; string[] s = ["Hello", "brave", "new", "world"]; string[] t = ["Hello", "new", "world"]; auto simIter = gapWeightedSimilarityIncremental(s, t, 1.0); //foreach (e; simIter) writeln(e); assert(simIter.front == 3); // three 1-length matches simIter.popFront(); assert(simIter.front == 3, text(simIter.front)); // three 2-length matches simIter.popFront(); assert(simIter.front == 1); // one 3-length matches simIter.popFront(); assert(simIter.empty); // no more match s = ["Hello"]; t = ["bye"]; simIter = gapWeightedSimilarityIncremental(s, t, 0.5); assert(simIter.empty); s = ["Hello"]; t = ["Hello"]; simIter = gapWeightedSimilarityIncremental(s, t, 0.5); assert(simIter.front == 1); // one match simIter.popFront(); assert(simIter.empty); s = ["Hello", "world"]; t = ["Hello"]; simIter = gapWeightedSimilarityIncremental(s, t, 0.5); assert(simIter.front == 1); // one match simIter.popFront(); assert(simIter.empty); s = ["Hello", "world"]; t = ["Hello", "yah", "world"]; simIter = gapWeightedSimilarityIncremental(s, t, 0.5); assert(simIter.front == 2); // two 1-gram matches simIter.popFront(); assert(simIter.front == 0.5, text(simIter.front)); // one 2-gram match, 1 gap } @system unittest { GapWeightedSimilarityIncremental!(string[]) sim = GapWeightedSimilarityIncremental!(string[])( ["nyuk", "I", "have", "no", "chocolate", "giba"], ["wyda", "I", "have", "I", "have", "have", "I", "have", "hehe"], 0.5); double[] witness = [ 7.0, 4.03125, 0, 0 ]; foreach (e; sim) { //writeln(e); assert(e == witness.front); witness.popFront(); } witness = [ 3.0, 1.3125, 0.25 ]; sim = GapWeightedSimilarityIncremental!(string[])( ["I", "have", "no", "chocolate"], ["I", "have", "some", "chocolate"], 0.5); foreach (e; sim) { //writeln(e); assert(e == witness.front); witness.popFront(); } assert(witness.empty); } /** Computes the greatest common divisor of `a` and `b` by using an efficient algorithm such as $(HTTPS en.wikipedia.org/wiki/Euclidean_algorithm, Euclid's) or $(HTTPS en.wikipedia.org/wiki/Binary_GCD_algorithm, Stein's) algorithm. Params: a = Integer value of any numerical type that supports the modulo operator `%`. If bit-shifting `<<` and `>>` are also supported, Stein's algorithm will be used; otherwise, Euclid's algorithm is used as _a fallback. b = Integer value of any equivalent numerical type. Returns: The greatest common divisor of the given arguments. */ typeof(Unqual!(T).init % Unqual!(U).init) gcd(T, U)(T a, U b) if (isIntegral!T && isIntegral!U) { // Operate on a common type between the two arguments. alias UCT = Unsigned!(CommonType!(Unqual!T, Unqual!U)); // `std.math.abs` doesn't support unsigned integers, and `T.min` is undefined. static if (is(T : immutable short) || is(T : immutable byte)) UCT ax = (isUnsigned!T || a >= 0) ? a : cast(UCT) -int(a); else UCT ax = (isUnsigned!T || a >= 0) ? a : -UCT(a); static if (is(U : immutable short) || is(U : immutable byte)) UCT bx = (isUnsigned!U || b >= 0) ? b : cast(UCT) -int(b); else UCT bx = (isUnsigned!U || b >= 0) ? b : -UCT(b); // Special cases. if (ax == 0) return bx; if (bx == 0) return ax; return gcdImpl(ax, bx); } private typeof(T.init % T.init) gcdImpl(T)(T a, T b) if (isIntegral!T) { pragma(inline, true); import core.bitop : bsf; import std.algorithm.mutation : swap; immutable uint shift = bsf(a | b); a >>= a.bsf; do { b >>= b.bsf; if (a > b) swap(a, b); b -= a; } while (b); return a << shift; } /// @safe unittest { assert(gcd(2 * 5 * 7 * 7, 5 * 7 * 11) == 5 * 7); const int a = 5 * 13 * 23 * 23, b = 13 * 59; assert(gcd(a, b) == 13); } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, const byte, const short, const int, const long, immutable ubyte, immutable ushort, immutable uint, immutable ulong)) { static foreach (U; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, const ubyte, const ushort, const uint, const ulong, immutable byte, immutable short, immutable int, immutable long)) { // Signed and unsigned tests. static if (T.max > byte.max && U.max > byte.max) assert(gcd(T(200), U(200)) == 200); static if (T.max > ubyte.max) { assert(gcd(T(2000), U(20)) == 20); assert(gcd(T(2011), U(17)) == 1); } static if (T.max > ubyte.max && U.max > ubyte.max) assert(gcd(T(1071), U(462)) == 21); assert(gcd(T(0), U(13)) == 13); assert(gcd(T(29), U(0)) == 29); assert(gcd(T(0), U(0)) == 0); assert(gcd(T(1), U(2)) == 1); assert(gcd(T(9), U(6)) == 3); assert(gcd(T(3), U(4)) == 1); assert(gcd(T(32), U(24)) == 8); assert(gcd(T(5), U(6)) == 1); assert(gcd(T(54), U(36)) == 18); // Int and Long tests. static if (T.max > short.max && U.max > short.max) assert(gcd(T(46391), U(62527)) == 2017); static if (T.max > ushort.max && U.max > ushort.max) assert(gcd(T(63245986), U(39088169)) == 1); static if (T.max > uint.max && U.max > uint.max) { assert(gcd(T(77160074263), U(47687519812)) == 1); assert(gcd(T(77160074264), U(47687519812)) == 4); } // Negative tests. static if (T.min < 0) { assert(gcd(T(-21), U(28)) == 7); assert(gcd(T(-3), U(4)) == 1); } static if (U.min < 0) { assert(gcd(T(1), U(-2)) == 1); assert(gcd(T(33), U(-44)) == 11); } static if (T.min < 0 && U.min < 0) { assert(gcd(T(-5), U(-6)) == 1); assert(gcd(T(-50), U(-60)) == 10); } } } } // https://issues.dlang.org/show_bug.cgi?id=21834 @safe unittest { assert(gcd(-120, 10U) == 10); assert(gcd(120U, -10) == 10); assert(gcd(int.min, 0L) == 1L + int.max); assert(gcd(0L, int.min) == 1L + int.max); assert(gcd(int.min, 0L + int.min) == 1L + int.max); assert(gcd(int.min, 1L + int.max) == 1L + int.max); assert(gcd(short.min, 1U + short.max) == 1U + short.max); } // This overload is for non-builtin numerical types like BigInt or // user-defined types. /// ditto auto gcd(T)(T a, T b) if (!isIntegral!T && is(typeof(T.init % T.init)) && is(typeof(T.init == 0 || T.init > 0))) { static if (!is(T == Unqual!T)) { return gcd!(Unqual!T)(a, b); } else { // Ensure arguments are unsigned. a = a >= 0 ? a : -a; b = b >= 0 ? b : -b; // Special cases. if (a == 0) return b; if (b == 0) return a; return gcdImpl(a, b); } } private auto gcdImpl(T)(T a, T b) if (!isIntegral!T) { pragma(inline, true); import std.algorithm.mutation : swap; enum canUseBinaryGcd = is(typeof(() { T t, u; t <<= 1; t >>= 1; t -= u; bool b = (t & 1) == 0; swap(t, u); })); static if (canUseBinaryGcd) { uint shift = 0; while ((a & 1) == 0 && (b & 1) == 0) { a >>= 1; b >>= 1; shift++; } if ((a & 1) == 0) swap(a, b); do { assert((a & 1) != 0); while ((b & 1) == 0) b >>= 1; if (a > b) swap(a, b); b -= a; } while (b); return a << shift; } else { // The only thing we have is %; fallback to Euclidean algorithm. while (b != 0) { auto t = b; b = a % b; a = t; } return a; } } // https://issues.dlang.org/show_bug.cgi?id=7102 @system pure unittest { import std.bigint : BigInt; assert(gcd(BigInt("71_000_000_000_000_000_000"), BigInt("31_000_000_000_000_000_000")) == BigInt("1_000_000_000_000_000_000")); assert(gcd(BigInt(0), BigInt(1234567)) == BigInt(1234567)); assert(gcd(BigInt(1234567), BigInt(0)) == BigInt(1234567)); } @safe pure nothrow unittest { // A numerical type that only supports % and - (to force gcd implementation // to use Euclidean algorithm). struct CrippledInt { int impl; CrippledInt opBinary(string op : "%")(CrippledInt i) { return CrippledInt(impl % i.impl); } CrippledInt opUnary(string op : "-")() { return CrippledInt(-impl); } int opEquals(CrippledInt i) { return impl == i.impl; } int opEquals(int i) { return impl == i; } int opCmp(int i) { return (impl < i) ? -1 : (impl > i) ? 1 : 0; } } assert(gcd(CrippledInt(2310), CrippledInt(1309)) == CrippledInt(77)); assert(gcd(CrippledInt(-120), CrippledInt(10U)) == CrippledInt(10)); assert(gcd(CrippledInt(120U), CrippledInt(-10)) == CrippledInt(10)); } // https://issues.dlang.org/show_bug.cgi?id=19514 @system pure unittest { import std.bigint : BigInt; assert(gcd(BigInt(2), BigInt(1)) == BigInt(1)); } // Issue 20924 @safe unittest { import std.bigint : BigInt; const a = BigInt("123143238472389492934020"); const b = BigInt("902380489324729338420924"); assert(__traits(compiles, gcd(a, b))); } // https://issues.dlang.org/show_bug.cgi?id=21834 @safe unittest { import std.bigint : BigInt; assert(gcd(BigInt(-120), BigInt(10U)) == BigInt(10)); assert(gcd(BigInt(120U), BigInt(-10)) == BigInt(10)); assert(gcd(BigInt(int.min), BigInt(0L)) == BigInt(1L + int.max)); assert(gcd(BigInt(0L), BigInt(int.min)) == BigInt(1L + int.max)); assert(gcd(BigInt(int.min), BigInt(0L + int.min)) == BigInt(1L + int.max)); assert(gcd(BigInt(int.min), BigInt(1L + int.max)) == BigInt(1L + int.max)); assert(gcd(BigInt(short.min), BigInt(1U + short.max)) == BigInt(1U + short.max)); } /** Computes the least common multiple of `a` and `b`. Arguments are the same as $(MYREF gcd). Returns: The least common multiple of the given arguments. */ typeof(Unqual!(T).init % Unqual!(U).init) lcm(T, U)(T a, U b) if (isIntegral!T && isIntegral!U) { // Operate on a common type between the two arguments. alias UCT = Unsigned!(CommonType!(Unqual!T, Unqual!U)); // `std.math.abs` doesn't support unsigned integers, and `T.min` is undefined. static if (is(T : immutable short) || is(T : immutable byte)) UCT ax = (isUnsigned!T || a >= 0) ? a : cast(UCT) -int(a); else UCT ax = (isUnsigned!T || a >= 0) ? a : -UCT(a); static if (is(U : immutable short) || is(U : immutable byte)) UCT bx = (isUnsigned!U || b >= 0) ? b : cast(UCT) -int(b); else UCT bx = (isUnsigned!U || b >= 0) ? b : -UCT(b); // Special cases. if (ax == 0) return ax; if (bx == 0) return bx; return (ax / gcdImpl(ax, bx)) * bx; } /// @safe unittest { assert(lcm(1, 2) == 2); assert(lcm(3, 4) == 12); assert(lcm(5, 6) == 30); } @safe unittest { import std.meta : AliasSeq; static foreach (T; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, const byte, const short, const int, const long, immutable ubyte, immutable ushort, immutable uint, immutable ulong)) { static foreach (U; AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, const ubyte, const ushort, const uint, const ulong, immutable byte, immutable short, immutable int, immutable long)) { assert(lcm(T(21), U(6)) == 42); assert(lcm(T(41), U(0)) == 0); assert(lcm(T(0), U(7)) == 0); assert(lcm(T(0), U(0)) == 0); assert(lcm(T(1U), U(2)) == 2); assert(lcm(T(3), U(4U)) == 12); assert(lcm(T(5U), U(6U)) == 30); static if (T.min < 0) assert(lcm(T(-42), U(21U)) == 42); } } } /// ditto auto lcm(T)(T a, T b) if (!isIntegral!T && is(typeof(T.init % T.init)) && is(typeof(T.init == 0 || T.init > 0))) { // Ensure arguments are unsigned. a = a >= 0 ? a : -a; b = b >= 0 ? b : -b; // Special cases. if (a == 0) return a; if (b == 0) return b; return (a / gcdImpl(a, b)) * b; } @safe unittest { import std.bigint : BigInt; assert(lcm(BigInt(21), BigInt(6)) == BigInt(42)); assert(lcm(BigInt(41), BigInt(0)) == BigInt(0)); assert(lcm(BigInt(0), BigInt(7)) == BigInt(0)); assert(lcm(BigInt(0), BigInt(0)) == BigInt(0)); assert(lcm(BigInt(1U), BigInt(2)) == BigInt(2)); assert(lcm(BigInt(3), BigInt(4U)) == BigInt(12)); assert(lcm(BigInt(5U), BigInt(6U)) == BigInt(30)); assert(lcm(BigInt(-42), BigInt(21U)) == BigInt(42)); } // This is to make tweaking the speed/size vs. accuracy tradeoff easy, // though floats seem accurate enough for all practical purposes, since // they pass the "isClose(inverseFft(fft(arr)), arr)" test even for // size 2 ^^ 22. private alias lookup_t = float; /**A class for performing fast Fourier transforms of power of two sizes. * This class encapsulates a large amount of state that is reusable when * performing multiple FFTs of sizes smaller than or equal to that specified * in the constructor. This results in substantial speedups when performing * multiple FFTs with a known maximum size. However, * a free function API is provided for convenience if you need to perform a * one-off FFT. * * References: * $(HTTP en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm) */ final class Fft { import core.bitop : bsf; import std.algorithm.iteration : map; import std.array : uninitializedArray; private: immutable lookup_t[][] negSinLookup; void enforceSize(R)(R range) const { import std.conv : text; assert(range.length <= size, text( "FFT size mismatch. Expected ", size, ", got ", range.length)); } void fftImpl(Ret, R)(Stride!R range, Ret buf) const in { assert(range.length >= 4); assert(isPowerOf2(range.length)); } do { auto recurseRange = range; recurseRange.doubleSteps(); if (buf.length > 4) { fftImpl(recurseRange, buf[0..$ / 2]); recurseRange.popHalf(); fftImpl(recurseRange, buf[$ / 2..$]); } else { // Do this here instead of in another recursion to save on // recursion overhead. slowFourier2(recurseRange, buf[0..$ / 2]); recurseRange.popHalf(); slowFourier2(recurseRange, buf[$ / 2..$]); } butterfly(buf); } // This algorithm works by performing the even and odd parts of our FFT // using the "two for the price of one" method mentioned at // http://www.engineeringproductivitytools.com/stuff/T0001/PT10.HTM#Head521 // by making the odd terms into the imaginary components of our new FFT, // and then using symmetry to recombine them. void fftImplPureReal(Ret, R)(R range, Ret buf) const in { assert(range.length >= 4); assert(isPowerOf2(range.length)); } do { alias E = ElementType!R; // Converts odd indices of range to the imaginary components of // a range half the size. The even indices become the real components. static if (isArray!R && isFloatingPoint!E) { // Then the memory layout of complex numbers provides a dirt // cheap way to convert. This is a common case, so take advantage. auto oddsImag = cast(Complex!E[]) range; } else { // General case: Use a higher order range. We can assume // source.length is even because it has to be a power of 2. static struct OddToImaginary { R source; alias C = Complex!(CommonType!(E, typeof(buf[0].re))); @property { C front() { return C(source[0], source[1]); } C back() { immutable n = source.length; return C(source[n - 2], source[n - 1]); } typeof(this) save() { return typeof(this)(source.save); } bool empty() { return source.empty; } size_t length() { return source.length / 2; } } void popFront() { source.popFront(); source.popFront(); } void popBack() { source.popBack(); source.popBack(); } C opIndex(size_t index) { return C(source[index * 2], source[index * 2 + 1]); } typeof(this) opSlice(size_t lower, size_t upper) { return typeof(this)(source[lower * 2 .. upper * 2]); } } auto oddsImag = OddToImaginary(range); } fft(oddsImag, buf[0..$ / 2]); auto evenFft = buf[0..$ / 2]; auto oddFft = buf[$ / 2..$]; immutable halfN = evenFft.length; oddFft[0].re = buf[0].im; oddFft[0].im = 0; evenFft[0].im = 0; // evenFft[0].re is already right b/c it's aliased with buf[0].re. foreach (k; 1 .. halfN / 2 + 1) { immutable bufk = buf[k]; immutable bufnk = buf[buf.length / 2 - k]; evenFft[k].re = 0.5 * (bufk.re + bufnk.re); evenFft[halfN - k].re = evenFft[k].re; evenFft[k].im = 0.5 * (bufk.im - bufnk.im); evenFft[halfN - k].im = -evenFft[k].im; oddFft[k].re = 0.5 * (bufk.im + bufnk.im); oddFft[halfN - k].re = oddFft[k].re; oddFft[k].im = 0.5 * (bufnk.re - bufk.re); oddFft[halfN - k].im = -oddFft[k].im; } butterfly(buf); } void butterfly(R)(R buf) const in { assert(isPowerOf2(buf.length)); } do { immutable n = buf.length; immutable localLookup = negSinLookup[bsf(n)]; assert(localLookup.length == n); immutable cosMask = n - 1; immutable cosAdd = n / 4 * 3; lookup_t negSinFromLookup(size_t index) pure nothrow { return localLookup[index]; } lookup_t cosFromLookup(size_t index) pure nothrow { // cos is just -sin shifted by PI * 3 / 2. return localLookup[(index + cosAdd) & cosMask]; } immutable halfLen = n / 2; // This loop is unrolled and the two iterations are interleaved // relative to the textbook FFT to increase ILP. This gives roughly 5% // speedups on DMD. for (size_t k = 0; k < halfLen; k += 2) { immutable cosTwiddle1 = cosFromLookup(k); immutable sinTwiddle1 = negSinFromLookup(k); immutable cosTwiddle2 = cosFromLookup(k + 1); immutable sinTwiddle2 = negSinFromLookup(k + 1); immutable realLower1 = buf[k].re; immutable imagLower1 = buf[k].im; immutable realLower2 = buf[k + 1].re; immutable imagLower2 = buf[k + 1].im; immutable upperIndex1 = k + halfLen; immutable upperIndex2 = upperIndex1 + 1; immutable realUpper1 = buf[upperIndex1].re; immutable imagUpper1 = buf[upperIndex1].im; immutable realUpper2 = buf[upperIndex2].re; immutable imagUpper2 = buf[upperIndex2].im; immutable realAdd1 = cosTwiddle1 * realUpper1 - sinTwiddle1 * imagUpper1; immutable imagAdd1 = sinTwiddle1 * realUpper1 + cosTwiddle1 * imagUpper1; immutable realAdd2 = cosTwiddle2 * realUpper2 - sinTwiddle2 * imagUpper2; immutable imagAdd2 = sinTwiddle2 * realUpper2 + cosTwiddle2 * imagUpper2; buf[k].re += realAdd1; buf[k].im += imagAdd1; buf[k + 1].re += realAdd2; buf[k + 1].im += imagAdd2; buf[upperIndex1].re = realLower1 - realAdd1; buf[upperIndex1].im = imagLower1 - imagAdd1; buf[upperIndex2].re = realLower2 - realAdd2; buf[upperIndex2].im = imagLower2 - imagAdd2; } } // This constructor is used within this module for allocating the // buffer space elsewhere besides the GC heap. It's definitely **NOT** // part of the public API and definitely **IS** subject to change. // // Also, this is unsafe because the memSpace buffer will be cast // to immutable. // // Public b/c of https://issues.dlang.org/show_bug.cgi?id=4636. public this(lookup_t[] memSpace) { immutable size = memSpace.length / 2; /* Create a lookup table of all negative sine values at a resolution of * size and all smaller power of two resolutions. This may seem * inefficient, but having all the lookups be next to each other in * memory at every level of iteration is a huge win performance-wise. */ if (size == 0) { return; } assert(isPowerOf2(size), "Can only do FFTs on ranges with a size that is a power of two."); auto table = new lookup_t[][bsf(size) + 1]; table[$ - 1] = memSpace[$ - size..$]; memSpace = memSpace[0 .. size]; auto lastRow = table[$ - 1]; lastRow[0] = 0; // -sin(0) == 0. foreach (ptrdiff_t i; 1 .. size) { // The hard coded cases are for improved accuracy and to prevent // annoying non-zeroness when stuff should be zero. if (i == size / 4) lastRow[i] = -1; // -sin(pi / 2) == -1. else if (i == size / 2) lastRow[i] = 0; // -sin(pi) == 0. else if (i == size * 3 / 4) lastRow[i] = 1; // -sin(pi * 3 / 2) == 1 else lastRow[i] = -sin(i * 2.0L * PI / size); } // Fill in all the other rows with strided versions. foreach (i; 1 .. table.length - 1) { immutable strideLength = size / (2 ^^ i); auto strided = Stride!(lookup_t[])(lastRow, strideLength); table[i] = memSpace[$ - strided.length..$]; memSpace = memSpace[0..$ - strided.length]; size_t copyIndex; foreach (elem; strided) { table[i][copyIndex++] = elem; } } negSinLookup = cast(immutable) table; } public: /**Create an `Fft` object for computing fast Fourier transforms of * power of two sizes of `size` or smaller. `size` must be a * power of two. */ this(size_t size) { // Allocate all twiddle factor buffers in one contiguous block so that, // when one is done being used, the next one is next in cache. auto memSpace = uninitializedArray!(lookup_t[])(2 * size); this(memSpace); } @property size_t size() const { return (negSinLookup is null) ? 0 : negSinLookup[$ - 1].length; } /**Compute the Fourier transform of range using the $(BIGOH N log N) * Cooley-Tukey Algorithm. `range` must be a random-access range with * slicing and a length equal to `size` as provided at the construction of * this object. The contents of range can be either numeric types, * which will be interpreted as pure real values, or complex types with * properties or members `.re` and `.im` that can be read. * * Note: Pure real FFTs are automatically detected and the relevant * optimizations are performed. * * Returns: An array of complex numbers representing the transformed data in * the frequency domain. * * Conventions: The exponent is negative and the factor is one, * i.e., output[j] := sum[ exp(-2 PI i j k / N) input[k] ]. */ Complex!F[] fft(F = double, R)(R range) const if (isFloatingPoint!F && isRandomAccessRange!R) { enforceSize(range); Complex!F[] ret; if (range.length == 0) { return ret; } // Don't waste time initializing the memory for ret. ret = uninitializedArray!(Complex!F[])(range.length); fft(range, ret); return ret; } /**Same as the overload, but allows for the results to be stored in a user- * provided buffer. The buffer must be of the same length as range, must be * a random-access range, must have slicing, and must contain elements that are * complex-like. This means that they must have a .re and a .im member or * property that can be both read and written and are floating point numbers. */ void fft(Ret, R)(R range, Ret buf) const if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) { assert(buf.length == range.length); enforceSize(range); if (range.length == 0) { return; } else if (range.length == 1) { buf[0] = range[0]; return; } else if (range.length == 2) { slowFourier2(range, buf); return; } else { alias E = ElementType!R; static if (is(E : real)) { return fftImplPureReal(range, buf); } else { static if (is(R : Stride!R)) return fftImpl(range, buf); else return fftImpl(Stride!R(range, 1), buf); } } } /** * Computes the inverse Fourier transform of a range. The range must be a * random access range with slicing, have a length equal to the size * provided at construction of this object, and contain elements that are * either of type std.complex.Complex or have essentially * the same compile-time interface. * * Returns: The time-domain signal. * * Conventions: The exponent is positive and the factor is 1/N, i.e., * output[j] := (1 / N) sum[ exp(+2 PI i j k / N) input[k] ]. */ Complex!F[] inverseFft(F = double, R)(R range) const if (isRandomAccessRange!R && isComplexLike!(ElementType!R) && isFloatingPoint!F) { enforceSize(range); Complex!F[] ret; if (range.length == 0) { return ret; } // Don't waste time initializing the memory for ret. ret = uninitializedArray!(Complex!F[])(range.length); inverseFft(range, ret); return ret; } /** * Inverse FFT that allows a user-supplied buffer to be provided. The buffer * must be a random access range with slicing, and its elements * must be some complex-like type. */ void inverseFft(Ret, R)(R range, Ret buf) const if (isRandomAccessRange!Ret && isComplexLike!(ElementType!Ret) && hasSlicing!Ret) { enforceSize(range); auto swapped = map!swapRealImag(range); fft(swapped, buf); immutable lenNeg1 = 1.0 / buf.length; foreach (ref elem; buf) { immutable temp = elem.re * lenNeg1; elem.re = elem.im * lenNeg1; elem.im = temp; } } } // This mixin creates an Fft object in the scope it's mixed into such that all // memory owned by the object is deterministically destroyed at the end of that // scope. private enum string MakeLocalFft = q{ import core.stdc.stdlib; import core.exception : onOutOfMemoryError; auto lookupBuf = (cast(lookup_t*) malloc(range.length * 2 * lookup_t.sizeof)) [0 .. 2 * range.length]; if (!lookupBuf.ptr) onOutOfMemoryError(); scope(exit) free(cast(void*) lookupBuf.ptr); auto fftObj = scoped!Fft(lookupBuf); }; /**Convenience functions that create an `Fft` object, run the FFT or inverse * FFT and return the result. Useful for one-off FFTs. * * Note: In addition to convenience, these functions are slightly more * efficient than manually creating an Fft object for a single use, * as the Fft object is deterministically destroyed before these * functions return. */ Complex!F[] fft(F = double, R)(R range) { mixin(MakeLocalFft); return fftObj.fft!(F, R)(range); } /// ditto void fft(Ret, R)(R range, Ret buf) { mixin(MakeLocalFft); return fftObj.fft!(Ret, R)(range, buf); } /// ditto Complex!F[] inverseFft(F = double, R)(R range) { mixin(MakeLocalFft); return fftObj.inverseFft!(F, R)(range); } /// ditto void inverseFft(Ret, R)(R range, Ret buf) { mixin(MakeLocalFft); return fftObj.inverseFft!(Ret, R)(range, buf); } @system unittest { import std.algorithm; import std.conv; import std.range; // Test values from R and Octave. auto arr = [1,2,3,4,5,6,7,8]; auto fft1 = fft(arr); assert(isClose(map!"a.re"(fft1), [36.0, -4, -4, -4, -4, -4, -4, -4], 1e-4)); assert(isClose(map!"a.im"(fft1), [0, 9.6568, 4, 1.6568, 0, -1.6568, -4, -9.6568], 1e-4)); auto fft1Retro = fft(retro(arr)); assert(isClose(map!"a.re"(fft1Retro), [36.0, 4, 4, 4, 4, 4, 4, 4], 1e-4)); assert(isClose(map!"a.im"(fft1Retro), [0, -9.6568, -4, -1.6568, 0, 1.6568, 4, 9.6568], 1e-4)); auto fft1Float = fft(to!(float[])(arr)); assert(isClose(map!"a.re"(fft1), map!"a.re"(fft1Float))); assert(isClose(map!"a.im"(fft1), map!"a.im"(fft1Float))); alias C = Complex!float; auto arr2 = [C(1,2), C(3,4), C(5,6), C(7,8), C(9,10), C(11,12), C(13,14), C(15,16)]; auto fft2 = fft(arr2); assert(isClose(map!"a.re"(fft2), [64.0, -27.3137, -16, -11.3137, -8, -4.6862, 0, 11.3137], 1e-4)); assert(isClose(map!"a.im"(fft2), [72, 11.3137, 0, -4.686, -8, -11.3137, -16, -27.3137], 1e-4)); auto inv1 = inverseFft(fft1); assert(isClose(map!"a.re"(inv1), arr, 1e-6)); assert(reduce!max(map!"a.im"(inv1)) < 1e-10); auto inv2 = inverseFft(fft2); assert(isClose(map!"a.re"(inv2), map!"a.re"(arr2))); assert(isClose(map!"a.im"(inv2), map!"a.im"(arr2))); // FFTs of size 0, 1 and 2 are handled as special cases. Test them here. ushort[] empty; assert(fft(empty) == null); assert(inverseFft(fft(empty)) == null); real[] oneElem = [4.5L]; auto oneFft = fft(oneElem); assert(oneFft.length == 1); assert(oneFft[0].re == 4.5L); assert(oneFft[0].im == 0); auto oneInv = inverseFft(oneFft); assert(oneInv.length == 1); assert(isClose(oneInv[0].re, 4.5)); assert(isClose(oneInv[0].im, 0, 0.0, 1e-10)); long[2] twoElems = [8, 4]; auto twoFft = fft(twoElems[]); assert(twoFft.length == 2); assert(isClose(twoFft[0].re, 12)); assert(isClose(twoFft[0].im, 0, 0.0, 1e-10)); assert(isClose(twoFft[1].re, 4)); assert(isClose(twoFft[1].im, 0, 0.0, 1e-10)); auto twoInv = inverseFft(twoFft); assert(isClose(twoInv[0].re, 8)); assert(isClose(twoInv[0].im, 0, 0.0, 1e-10)); assert(isClose(twoInv[1].re, 4)); assert(isClose(twoInv[1].im, 0, 0.0, 1e-10)); } // Swaps the real and imaginary parts of a complex number. This is useful // for inverse FFTs. C swapRealImag(C)(C input) { return C(input.im, input.re); } /** This function transforms `decimal` value into a value in the factorial number system stored in `fac`. A factorial number is constructed as: $(D fac[0] * 0! + fac[1] * 1! + ... fac[20] * 20!) Params: decimal = The decimal value to convert into the factorial number system. fac = The array to store the factorial number. The array is of size 21 as `ulong.max` requires 21 digits in the factorial number system. Returns: A variable storing the number of digits of the factorial number stored in `fac`. */ size_t decimalToFactorial(ulong decimal, ref ubyte[21] fac) @safe pure nothrow @nogc { import std.algorithm.mutation : reverse; size_t idx; for (ulong i = 1; decimal != 0; ++i) { auto temp = decimal % i; decimal /= i; fac[idx++] = cast(ubyte)(temp); } if (idx == 0) { fac[idx++] = cast(ubyte) 0; } reverse(fac[0 .. idx]); // first digit of the number in factorial will always be zero assert(fac[idx - 1] == 0); return idx; } /// @safe pure @nogc unittest { ubyte[21] fac; size_t idx = decimalToFactorial(2982, fac); assert(fac[0] == 4); assert(fac[1] == 0); assert(fac[2] == 4); assert(fac[3] == 1); assert(fac[4] == 0); assert(fac[5] == 0); assert(fac[6] == 0); } @safe pure unittest { ubyte[21] fac; size_t idx = decimalToFactorial(0UL, fac); assert(idx == 1); assert(fac[0] == 0); fac[] = 0; idx = 0; idx = decimalToFactorial(ulong.max, fac); assert(idx == 21); auto t = [7, 11, 12, 4, 3, 15, 3, 5, 3, 5, 0, 8, 3, 5, 0, 0, 0, 2, 1, 1, 0]; foreach (i, it; fac[0 .. 21]) { assert(it == t[i]); } fac[] = 0; idx = decimalToFactorial(2982, fac); assert(idx == 7); t = [4, 0, 4, 1, 0, 0, 0]; foreach (i, it; fac[0 .. idx]) { assert(it == t[i]); } } private: // The reasons I couldn't use std.algorithm were b/c its stride length isn't // modifiable on the fly and because range has grown some performance hacks // for powers of 2. struct Stride(R) { import core.bitop : bsf; Unqual!R range; size_t _nSteps; size_t _length; alias E = ElementType!(R); this(R range, size_t nStepsIn) { this.range = range; _nSteps = nStepsIn; _length = (range.length + _nSteps - 1) / nSteps; } size_t length() const @property { return _length; } typeof(this) save() @property { auto ret = this; ret.range = ret.range.save; return ret; } E opIndex(size_t index) { return range[index * _nSteps]; } E front() @property { return range[0]; } void popFront() { if (range.length >= _nSteps) { range = range[_nSteps .. range.length]; _length--; } else { range = range[0 .. 0]; _length = 0; } } // Pops half the range's stride. void popHalf() { range = range[_nSteps / 2 .. range.length]; } bool empty() const @property { return length == 0; } size_t nSteps() const @property { return _nSteps; } void doubleSteps() { _nSteps *= 2; _length /= 2; } size_t nSteps(size_t newVal) @property { _nSteps = newVal; // Using >> bsf(nSteps) is a few cycles faster than / nSteps. _length = (range.length + _nSteps - 1) >> bsf(nSteps); return newVal; } } // Hard-coded base case for FFT of size 2. This is actually a TON faster than // using a generic slow DFT. This seems to be the best base case. (Size 1 // can be coded inline as buf[0] = range[0]). void slowFourier2(Ret, R)(R range, Ret buf) { assert(range.length == 2); assert(buf.length == 2); buf[0] = range[0] + range[1]; buf[1] = range[0] - range[1]; } // Hard-coded base case for FFT of size 4. Doesn't work as well as the size // 2 case. void slowFourier4(Ret, R)(R range, Ret buf) { alias C = ElementType!Ret; assert(range.length == 4); assert(buf.length == 4); buf[0] = range[0] + range[1] + range[2] + range[3]; buf[1] = range[0] - range[1] * C(0, 1) - range[2] + range[3] * C(0, 1); buf[2] = range[0] - range[1] + range[2] - range[3]; buf[3] = range[0] + range[1] * C(0, 1) - range[2] - range[3] * C(0, 1); } N roundDownToPowerOf2(N)(N num) if (isScalarType!N && !isFloatingPoint!N) { import core.bitop : bsr; return num & (cast(N) 1 << bsr(num)); } @safe unittest { assert(roundDownToPowerOf2(7) == 4); assert(roundDownToPowerOf2(4) == 4); } template isComplexLike(T) { enum bool isComplexLike = is(typeof(T.init.re)) && is(typeof(T.init.im)); } @safe unittest { static assert(isComplexLike!(Complex!double)); static assert(!isComplexLike!(uint)); }