Skip to content

Commit

Permalink
Merge pull request #3022 from ivan-timokhin/totalOrder
Browse files Browse the repository at this point in the history
Total ordering for floating-point values.
  • Loading branch information
DmitryOlshansky committed Sep 2, 2015
2 parents 1e647d9 + 9f5aa69 commit f2220c2
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 0 deletions.
16 changes: 16 additions & 0 deletions std/algorithm/sorting.d
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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;
Expand Down
229 changes: 229 additions & 0 deletions std/math.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
}

0 comments on commit f2220c2

Please sign in to comment.