| // 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, IEEE754) 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 enum isIEEEQuadruple = floatTraits!real.realFormat == RealFormat.ieeeQuadruple; | |
| private template CustomFloatParams(uint bits) | |
| { | |
| enum CustomFloatFlags flags = CustomFloatFlags.ieee | |
| ^ ((bits == 80 && !isIEEEQuadruple) ? 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) const | |
| { | |
| 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 | |
| { | |
| static if (isIEEEQuadruple) | |
| { | |
| // Only use highest 64 significand bits from 112 explicitly stored | |
| align (1): | |
| enum ulong significand_max = ulong.max; | |
| version (LittleEndian) | |
| { | |
| private ubyte[6] _padding; // 48-bit of padding | |
| ulong significand; | |
| mixin(bitfields!( | |
| T_exp , "exponent", exponentWidth, | |
| bool , "sign" , flags & flags.signed )); | |
| } | |
| else | |
| { | |
| mixin(bitfields!( | |
| T_exp , "exponent", exponentWidth, | |
| bool , "sign" , flags & flags.signed )); | |
| ulong significand; | |
| private ubyte[6] _padding; // 48-bit of padding | |
| } | |
| } | |
| else | |
| { | |
| 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(real(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 10<sup>max_10_exp</sup> is representable | |
| static @property int max_10_exp(){ return cast(int) log10( +max ); } | |
| /// maximum int value such that 2<sup>max_exp-1</sup> is representable | |
| enum max_exp = exponent_max - bias - ((flags & (Flags.infinity | Flags.nan)) != 0) + 1; | |
| /// Returns: minimum int value such that 10<sup>min_10_exp</sup> is representable | |
| static @property int min_10_exp(){ return cast(int) log10( +min_normal ); } | |
| /// minimum int value such that 2<sup>min_exp-1</sup> 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() const { 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)() const | |
| 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) const | |
| 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) const | |
| 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) const | |
| 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) const | |
| 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); | |
| assert(x.get!real == 0.125L); | |
| x -= 0.0625; | |
| assert(x.get!float == 0.0625F); | |
| assert(x.get!double == 0.0625); | |
| assert(x.get!real == 0.0625L); | |
| x *= 2; | |
| assert(x.get!float == 0.125F); | |
| assert(x.get!double == 0.125); | |
| assert(x.get!real == 0.125L); | |
| x /= 4; | |
| assert(x.get!float == 0.03125); | |
| assert(x.get!double == 0.03125); | |
| assert(x.get!real == 0.03125L); | |
| x = 0.5; | |
| x ^^= 4; | |
| assert(x.get!float == 1 / 16.0F); | |
| assert(x.get!double == 1 / 16.0); | |
| assert(x.get!real == 1 / 16.0L); | |
| } | |
| } | |
| @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); | |
| } | |
| @safe unittest | |
| { | |
| const CustomFloat!16 x = CustomFloat!16(3); | |
| assert(x.get!float == 3); | |
| assert(x.re.get!float == 3); | |
| assert(x + x == 6); | |
| assert(x + 1 == 4); | |
| assert(2 + x == 5); | |
| assert(x < 4); | |
| } | |
| 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 | |
| // https://web.archive.org/web/20180312110051/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) | |
| { | |
| static if (isNumeric!(ElementType!R)) | |
| { | |
| buf[0].re = range[0]; | |
| buf[0].im = 0; | |
| } | |
| else | |
| { | |
| buf[0].re = range[0].re; | |
| buf[0].im = range[0].im; | |
| } | |
| 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)); | |
| } | |
| // https://github.com/dlang/phobos/issues/10796 | |
| @system unittest | |
| { | |
| import std.algorithm; | |
| import std.range; | |
| static struct C { float re, im; } // User-defined complex | |
| float[8] arr = [1,2,3,4,5,6,7,8]; | |
| C[8] fft1; | |
| fft(arr[], fft1[]); | |
| assert(isClose(fft1[].map!"a.re", | |
| [36.0, -4, -4, -4, -4, -4, -4, -4], 1e-4)); | |
| assert(isClose(fft1[].map!"a.im", | |
| [0, 9.6568, 4, 1.6568, 0, -1.6568, -4, -9.6568], 1e-4)); | |
| auto inv = inverseFft(fft1[]); | |
| assert(isClose(inv[].map!"a.re", arr[], 1e-6)); | |
| assert(inv[].map!"a.im".maxElement < 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) | |
| if (isComplexLike!(ElementType!Ret)) | |
| in (range.length == 2) | |
| in (buf.length == 2) | |
| { | |
| static if (isNumeric!(ElementType!R)) | |
| { | |
| buf[0].re = range[0] + range[1]; | |
| buf[0].im = 0; | |
| buf[1].re = range[0] - range[1]; | |
| buf[1].im = 0; | |
| } | |
| else | |
| { | |
| buf[0].re = range[0].re + range[1].re; | |
| buf[0].im = range[0].im + range[1].im; | |
| buf[1].re = range[0].re - range[1].re; | |
| buf[1].im = range[0].im - range[1].im; | |
| } | |
| } | |
| // 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)); | |
| } |