Showing with 115 additions and 1 deletion.
  1. +23 −0 changelog.dd
  2. +92 −1 std/math.d
23 changes: 23 additions & 0 deletions changelog.dd
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ $(BUGSTITLE Library Changes,
`std.algorithm.searching.{min,max}Element` for ranges have been added.))
$(LI $(REF Ternary, std,typecons) was added to represent three valued
logic.)
$(LI $(RELATIVE_LINK2 quantize, Added `std.math.quantize`, for rounding to
the nearest multiple of some number.))
)

$(BUGSTITLE Library Changes,
Expand Down Expand Up @@ -243,6 +245,27 @@ assert([4, 7, 5].enumerate.maxElement!`a.value` == tuple(1, 7));
-------
)

$(LI $(LNAME2 quantize, Added `std.math.quantize`.)
$(P $(XREF math, quantize) rounds to the nearest multiple of some number.
Features:)
$(UL
$(LI The rounding method can be specified using the `rfunc` parameter. By
default, the current rounding mode will be used, which is typically
"banker's rounding".)
$(LI Overloads are included for the common case of rounding to the $(I Nth)
digit place in some base.)
$(LI If known at compile time, the base and exponent (digit place) can be
supplied as template parameters for better performance.)
)
---
import std.math;

assert(12345.6789L.quantize(20.0L) == 12340.0L);
assert(12345.6789L.quantize!(10, -2) == 12345.68L);
assert(12345.6789L.quantize!(10, floor)(-2) == 12345.67L);
---
)

)

Macros:
Expand Down
93 changes: 92 additions & 1 deletion std/math.d
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ $(TR $(TDNW Trigonometry) $(TD
$(TR $(TDNW Rounding) $(TD
$(MYREF ceil) $(MYREF floor) $(MYREF round) $(MYREF lround)
$(MYREF trunc) $(MYREF rint) $(MYREF lrint) $(MYREF nearbyint)
$(MYREF rndtol)
$(MYREF rndtol) $(MYREF quantize)
))
$(TR $(TDNW Exponentiation & Logarithms) $(TD
$(MYREF pow) $(MYREF exp) $(MYREF exp2) $(MYREF expm1) $(MYREF ldexp)
Expand Down Expand Up @@ -3933,6 +3933,97 @@ float floor(float x) @trusted pure nothrow @nogc
assert(isNaN(floor(float.init)));
}

/**
* Round `val` to a multiple of `unit`. `rfunc` specifies the rounding
* function to use; by default this is `rint`, which uses the current
* rounding mode.
*/
Unqual!F quantize(alias rfunc = rint, F)(const F val, const F unit)
if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F)
{
typeof(return) ret = val;
if (unit != 0)
{
const scaled = val / unit;
if (!scaled.isInfinity)
ret = rfunc(scaled) * unit;
}
return ret;
}

///
@safe pure nothrow @nogc unittest
{
assert(12345.6789L.quantize(0.01L) == 12345.68L);
assert(12345.6789L.quantize!floor(0.01L) == 12345.67L);
assert(12345.6789L.quantize(22.0L) == 12342.0L);
}

///
@safe pure nothrow @nogc unittest
{
assert(12345.6789L.quantize(0) == 12345.6789L);
assert(12345.6789L.quantize(real.infinity).isNaN);
assert(12345.6789L.quantize(real.nan).isNaN);
assert(real.infinity.quantize(0.01L) == real.infinity);
assert(real.infinity.quantize(real.nan).isNaN);
assert(real.nan.quantize(0.01L).isNaN);
assert(real.nan.quantize(real.infinity).isNaN);
assert(real.nan.quantize(real.nan).isNaN);
}

/**
* Round `val` to a multiple of `pow(base, exp)`. `rfunc` specifies the
* rounding function to use; by default this is `rint`, which uses the
* current rounding mode.
*/
Unqual!F quantize(real base, alias rfunc = rint, F, E)(const F val, const E exp)
if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F && isIntegral!E)
{
// TODO: Compile-time optimization for power-of-two bases?
return quantize!rfunc(val, pow(cast(F)base, exp));
}

/// ditto
Unqual!F quantize(real base, long exp = 1, alias rfunc = rint, F)(const F val)
if (is(typeof(rfunc(F.init)) : F) && isFloatingPoint!F)
{
enum unit = cast(F)pow(base, exp);
return quantize!rfunc(val, unit);
}

///
@safe pure nothrow @nogc unittest
{
assert(12345.6789L.quantize!10(-2) == 12345.68L);
assert(12345.6789L.quantize!(10, -2) == 12345.68L);
assert(12345.6789L.quantize!(10, floor)(-2) == 12345.67L);
assert(12345.6789L.quantize!(10, -2, floor) == 12345.67L);

assert(12345.6789L.quantize!22(1) == 12342.0L);
assert(12345.6789L.quantize!22 == 12342.0L);
}

@safe pure nothrow @nogc unittest
{
import std.meta : AliasSeq;

foreach (F; AliasSeq!(real, double, float))
{
const maxL10 = cast(int) F.max.log10.floor;
const maxR10 = pow(cast(F)10, maxL10);
assert((cast(F) 0.9L * maxR10).quantize!10(maxL10) == maxR10);
assert((cast(F)-0.9L * maxR10).quantize!10(maxL10) == -maxR10);

assert(F.max.quantize(F.min_normal) == F.max);
assert((-F.max).quantize(F.min_normal) == -F.max);
assert(F.min_normal.quantize(F.max) == 0);
assert((-F.min_normal).quantize(F.max) == 0);
assert(F.min_normal.quantize(F.min_normal) == F.min_normal);
assert((-F.min_normal).quantize(F.min_normal) == -F.min_normal);
}
}

/******************************************
* Rounds x to the nearest integer value, using the current rounding
* mode.
Expand Down