mirror of
https://github.com/dlang/phobos.git
synced 2025-05-07 03:27:03 +03:00
Merge pull request #3022 from ivan-timokhin/totalOrder
Total ordering for floating-point values.
This commit is contained in:
commit
f2220c2139
2 changed files with 245 additions and 0 deletions
|
@ -933,6 +933,7 @@ $(D less(a,c)) (transitivity), and, conversely, $(D !less(a,b) && !less(b,c)) to
|
|||
imply $(D !less(a,c)). Note that the default predicate ($(D "a < b")) does not
|
||||
always satisfy these conditions for floating point types, because the expression
|
||||
will always be $(D false) when either $(D a) or $(D b) is NaN.
|
||||
Use $(XREF math, cmp) instead.
|
||||
|
||||
Returns: The initial range wrapped as a $(D SortedRange) with the predicate
|
||||
$(D binaryFun!less).
|
||||
|
@ -1010,6 +1011,21 @@ unittest
|
|||
assert(words == [ "a", "aBc", "abc", "ABC", "b", "c" ]);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
// Sorting floating-point numbers in presence of NaN
|
||||
double[] numbers = [-0.0, 3.0, -2.0, double.nan, 0.0, -double.nan];
|
||||
|
||||
import std.math : cmp, isIdentical;
|
||||
import std.algorithm.comparison : equal;
|
||||
|
||||
sort!((a, b) => cmp(a, b) < 0)(numbers);
|
||||
|
||||
double[] sorted = [-double.nan, -2.0, -0.0, 0.0, 3.0, double.nan];
|
||||
assert(numbers.equal!isIdentical(sorted));
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import std.algorithm.internal : rndstuff;
|
||||
|
|
229
std/math.d
229
std/math.d
|
@ -41,6 +41,7 @@ $(TR $(TDNW Floating-point operations) $(TD
|
|||
$(MYREF approxEqual) $(MYREF feqrel) $(MYREF fdim) $(MYREF fmax)
|
||||
$(MYREF fmin) $(MYREF fma) $(MYREF nextDown) $(MYREF nextUp)
|
||||
$(MYREF nextafter) $(MYREF NaN) $(MYREF getNaNPayload)
|
||||
$(MYREF cmp)
|
||||
))
|
||||
$(TR $(TDNW Introspection) $(TD
|
||||
$(MYREF isFinite) $(MYREF isIdentical) $(MYREF isInfinity) $(MYREF isNaN)
|
||||
|
@ -7061,3 +7062,231 @@ real yl2xp1(real x, real y) @nogc @safe pure nothrow; // y * log2(x + 1)
|
|||
auto x = floor(1.2);
|
||||
auto y = ceil(1.2);
|
||||
}
|
||||
|
||||
/***********************************
|
||||
* Defines a total order on all floating-point numbers.
|
||||
*
|
||||
* The order is defined as follows:
|
||||
* $(UL
|
||||
* $(LI All numbers in [-$(INFIN), +$(INFIN)] are ordered
|
||||
* the same way as by built-in comparison, with the exception of
|
||||
* -0.0, which is less than +0.0;)
|
||||
* $(LI If the sign bit is set (that is, it's 'negative'), $(NAN) is less
|
||||
* than any number; if the sign bit is not set (it is 'positive'),
|
||||
* $(NAN) is greater than any number;)
|
||||
* $(LI $(NAN)s of the same sign are ordered by the payload ('negative'
|
||||
* ones - in reverse order).)
|
||||
* )
|
||||
*
|
||||
* Returns:
|
||||
* negative value if $(D x) precedes $(D y) in the order specified above;
|
||||
* 0 if $(D x) and $(D y) are identical, and positive value otherwise.
|
||||
*
|
||||
* See_Also:
|
||||
* $(MYREF isIdentical)
|
||||
* Standards: Conforms to IEEE 754-2008
|
||||
*/
|
||||
int cmp(T)(const(T) x, const(T) y) @nogc @trusted pure nothrow
|
||||
if (isFloatingPoint!T)
|
||||
{
|
||||
alias F = floatTraits!T;
|
||||
|
||||
static if (F.realFormat == RealFormat.ieeeSingle
|
||||
|| F.realFormat == RealFormat.ieeeDouble)
|
||||
{
|
||||
static if (T.sizeof == 4)
|
||||
alias UInt = uint;
|
||||
else
|
||||
alias UInt = ulong;
|
||||
|
||||
union Repainter
|
||||
{
|
||||
T number;
|
||||
UInt bits;
|
||||
}
|
||||
|
||||
enum msb = ~(UInt.max >>> 1);
|
||||
|
||||
import std.typecons : Tuple;
|
||||
Tuple!(Repainter, Repainter) vars = void;
|
||||
vars[0].number = x;
|
||||
vars[1].number = y;
|
||||
|
||||
foreach (ref var; vars)
|
||||
if (var.bits & msb)
|
||||
var.bits = ~var.bits;
|
||||
else
|
||||
var.bits |= msb;
|
||||
|
||||
if (vars[0].bits < vars[1].bits)
|
||||
return -1;
|
||||
else if (vars[0].bits > vars[1].bits)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else static if (F.realFormat == RealFormat.ieeeExtended53
|
||||
|| F.realFormat == RealFormat.ieeeExtended
|
||||
|| F.realFormat == RealFormat.ieeeQuadruple)
|
||||
{
|
||||
static if (F.realFormat == RealFormat.ieeeQuadruple)
|
||||
alias RemT = ulong;
|
||||
else
|
||||
alias RemT = ushort;
|
||||
|
||||
struct Bits
|
||||
{
|
||||
ulong bulk;
|
||||
RemT rem;
|
||||
}
|
||||
|
||||
union Repainter
|
||||
{
|
||||
T number;
|
||||
Bits bits;
|
||||
ubyte[T.sizeof] bytes;
|
||||
}
|
||||
|
||||
import std.typecons : Tuple;
|
||||
Tuple!(Repainter, Repainter) vars = void;
|
||||
vars[0].number = x;
|
||||
vars[1].number = y;
|
||||
|
||||
foreach (ref var; vars)
|
||||
if (var.bytes[F.SIGNPOS_BYTE] & 0x80)
|
||||
{
|
||||
var.bits.bulk = ~var.bits.bulk;
|
||||
var.bits.rem = ~var.bits.rem;
|
||||
}
|
||||
else
|
||||
{
|
||||
var.bytes[F.SIGNPOS_BYTE] |= 0x80;
|
||||
}
|
||||
|
||||
version(LittleEndian)
|
||||
{
|
||||
if (vars[0].bits.rem < vars[1].bits.rem)
|
||||
return -1;
|
||||
else if (vars[0].bits.rem > vars[1].bits.rem)
|
||||
return 1;
|
||||
else if (vars[0].bits.bulk < vars[1].bits.bulk)
|
||||
return -1;
|
||||
else if (vars[0].bits.bulk > vars[1].bits.bulk)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vars[0].bits.bulk < vars[1].bits.bulk)
|
||||
return -1;
|
||||
else if (vars[0].bits.bulk > vars[1].bits.bulk)
|
||||
return 1;
|
||||
else if (vars[0].bits.rem < vars[1].bits.rem)
|
||||
return -1;
|
||||
else if (vars[0].bits.rem > vars[1].bits.rem)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// IBM Extended doubledouble does not follow the general
|
||||
// sign-exponent-significand layout, so has to be handled generically
|
||||
|
||||
int xSign = signbit(x),
|
||||
ySign = signbit(y);
|
||||
|
||||
if (xSign == 1 && ySign == 1)
|
||||
return cmp(-y, -x);
|
||||
else if (xSign == 1)
|
||||
return -1;
|
||||
else if (ySign == 1)
|
||||
return 1;
|
||||
else if (x < y)
|
||||
return -1;
|
||||
else if (x == y)
|
||||
return 0;
|
||||
else if (x > y)
|
||||
return 1;
|
||||
else if (isNaN(x) && !isNaN(y))
|
||||
return 1;
|
||||
else if (isNaN(y) && !isNaN(x))
|
||||
return -1;
|
||||
else if (getNaNPayload(x) < getNaNPayload(y))
|
||||
return -1;
|
||||
else if (getNaNPayload(x) > getNaNPayload(y))
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Most numbers are ordered naturally.
|
||||
unittest
|
||||
{
|
||||
assert(cmp(-double.infinity, -double.max) < 0);
|
||||
assert(cmp(-double.max, -100.0) < 0);
|
||||
assert(cmp(-100.0, -0.5) < 0);
|
||||
assert(cmp(-0.5, 0.0) < 0);
|
||||
assert(cmp(0.0, 0.5) < 0);
|
||||
assert(cmp(0.5, 100.0) < 0);
|
||||
assert(cmp(100.0, double.max) < 0);
|
||||
assert(cmp(double.max, double.infinity) < 0);
|
||||
|
||||
assert(cmp(1.0, 1.0) == 0);
|
||||
}
|
||||
|
||||
/// Positive and negative zeroes are distinct.
|
||||
unittest
|
||||
{
|
||||
assert(cmp(-0.0, +0.0) < 0);
|
||||
assert(cmp(+0.0, -0.0) > 0);
|
||||
}
|
||||
|
||||
/// Depending on the sign, $(NAN)s go to either end of the spectrum.
|
||||
unittest
|
||||
{
|
||||
assert(cmp(-double.nan, -double.infinity) < 0);
|
||||
assert(cmp(double.infinity, double.nan) < 0);
|
||||
assert(cmp(-double.nan, double.nan) < 0);
|
||||
}
|
||||
|
||||
/// $(NAN)s of the same sign are ordered by the payload.
|
||||
unittest
|
||||
{
|
||||
assert(cmp(NaN(10), NaN(20)) < 0);
|
||||
assert(cmp(-NaN(20), -NaN(10)) < 0);
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
import std.typetuple;
|
||||
foreach (T; TypeTuple!(float, double, real))
|
||||
{
|
||||
T[] values = [-cast(T)NaN(20), -cast(T)NaN(10), -T.nan, -T.infinity,
|
||||
-T.max, -T.max / 2, T(-16.0), T(-1.0).nextDown,
|
||||
T(-1.0), T(-1.0).nextUp,
|
||||
T(-0.5), -T.min_normal, (-T.min_normal).nextUp,
|
||||
-2 * T.min_normal * T.epsilon,
|
||||
-T.min_normal * T.epsilon,
|
||||
T(-0.0), T(0.0),
|
||||
T.min_normal * T.epsilon,
|
||||
2 * T.min_normal * T.epsilon,
|
||||
T.min_normal.nextDown, T.min_normal, T(0.5),
|
||||
T(1.0).nextDown, T(1.0),
|
||||
T(1.0).nextUp, T(16.0), T.max / 2, T.max,
|
||||
T.infinity, T.nan, cast(T)NaN(10), cast(T)NaN(20)];
|
||||
|
||||
foreach (i, x; values)
|
||||
{
|
||||
foreach (y; values[i + 1 .. $])
|
||||
{
|
||||
assert(cmp(x, y) < 0);
|
||||
assert(cmp(y, x) > 0);
|
||||
}
|
||||
assert(cmp(x, x) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue