Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a D implementation for std.math.ilogb #3186

Merged
merged 2 commits into from
Aug 2, 2015

Conversation

JohanEngelen
Copy link
Contributor

The reasons for this PR:

  • Gets rid of assembly code that is DMD-specific and does not work for LDC. Special case for Windows CRT is no longer needed.
  • Avoiding the x87 FPU means this implementation is faster than the assembly code.
  • Not calling C-runtime: ilogb can be inlined and the compiler can reason about it.
  • Templated so it runs faster for floats and doubles

Some quick benchmarking showed that it is indeed quite a bit faster, especially (not surprising) for float and double. But the first reason is my main motivation.

I do not know how to do the casts to enable CTFE.
I added a few extra unittests to increase code coverage.

(edit: removed wrong comment about ARM)

@@ -2519,6 +2519,21 @@ unittest
}
}

private import core.bitop : bsr;
static if (2*size_t.sizeof == ulong.sizeof)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you really sure this is actually needed?

@JohanEngelen
Copy link
Contributor Author

@Orvid The code does need a bsr that works for 64-bit numbers on all platforms (32 and 64 bit), which druntime does not provide. But you are right: it is not needed outside ilogb. To fix further function name resolution problems, I've renamed the 64-bit bsr to bsr_ulong.

I've rebased the changes into one commit.

ret ;
size_t* uix = cast(size_t*)&x;
size_t msb = uix[MANTISSA_MSB];
size_t lsb = uix[MANTISSA_LSB];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I would prefer

uint msb = x >> 32;
if (msb)
    return bsr(msb) + 32;
else
    return bsr(cast(uint)x);

Over pointer casting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.
That makes it CTFE-able I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Safety0ff That also gets rid of Endianness-noise. I've amended the PR.

@JohanEngelen JohanEngelen force-pushed the new_ilogb branch 2 times, most recently from af51714 to 55a2a8d Compare April 13, 2015 18:52
@JohanEngelen
Copy link
Contributor Author

The testbot failure is (as far as I can see) completely unrelated to this PR.

version (Win64_DMD_InlineAsm)
import core.bitop : bsr;
// Provide a bsr implementation for ulong in 32-bit mode
int bsr_ulong(ulong x) @trusted pure nothrow @nogc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this function outside from ilogb?

static import core.bitop.bsr;
...
static if(size_t.sizeof == 4)
{
   int bsr_ulong(ulong x) @trusted pure nothrow @nogc
    {
         ...
    }
}
else
    private alias bsr_ulong = core.bitop.bsr;

The reason is to reduce template bloat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also a bsr64 implementation in std.numeric - it's private, however... https://github.com/D-Programming-Language/phobos/blob/965744f7d010082a8e2a68c3ec45734f5d845aff/std/numeric.d#L85

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... it is outdated. Looks like we need package bsr64 in std.math.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ibuclaw How sure are you that the bsr64 in std.numeric works for both little endian and big endian? Using a union instead of bitshifts makes a large assumption about memory layout that I am not sure is justified.

static if (F.realFormat == RealFormat.ieeeExtended)
{
if (ex)
{ // If exponent is non-zero
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do believe the coding style of Phobos discourages comments nestled in on the same line as opening braces. Please move this and others on the next line.

@JohanEngelen
Copy link
Contributor Author

@ibuclaw and @9il Thanks for review, I've improved the changes, rebased, and are waiting for Travis results.

ushort[T.sizeof/2] vu;
uint[T.sizeof/4] vui;
static if(T.sizeof >= 8)
long[T.sizeof/8] vl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, T.sizeof could be 12. So a more complicated logic other than dividing by 8 is required. What I may do in future is move this to be a part of floatTraits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of T.sizeof == 12 or 10, I was assuming this would truncate to long[1], which is fine? (the code knows about this)
edit: because real.sizeof==10, this case is tested already and seems to work :)

edit2: OK, I did not think this through thoroughly... there is the potential for problems with memory layout of union members smaller than the union size. The original code (parts from frexp) may have a similar problem with bigendian/littleendian with 80bit reals. However, in case of trouble, the unittests will fail.

@JohanEngelen
Copy link
Contributor Author

rebased for cleaner history.

@JohanEngelen
Copy link
Contributor Author

removed trailing whitespace, and rebased

@9il
Copy link
Member

9il commented Jul 20, 2015

Take a look at #3499 .

@JohanEngelen
Copy link
Contributor Author

@9il Do you mean that this PR should implement the same as #3499 ? That sounds backwards. I think this PR should be merged (it's been sitting here for 2+ months). Then when #3499 settles it can apply the same changes from frexp to ilogb.

@9il
Copy link
Member

9il commented Jul 22, 2015

No, only FYI. It can be merged with current implementation, thought.

@JohanEngelen
Copy link
Contributor Author

OK, sorry for my half-angry comment... :(
Thanks for the heads up. #3499 is certainly an improvement over the current hack (at least it concentrates the hack to one central point ;)

@DmitryOlshansky
Copy link
Member

@9il anything to review here or is it good to go?

@9il
Copy link
Member

9il commented Jul 28, 2015

LGTM except unittest.
@JohanEngelen Please generalise it with

foreach(F; TypeTuple!(float, double real))
{
...
}

@JohanEngelen
Copy link
Contributor Author

@9il I've tried to generalize the unittests, using a construct found elsewhere in math.d. (I am very new to D) Thanks for the patience.

@9il
Copy link
Member

9il commented Jul 28, 2015

Thank you for math PRs :)
Rebase all commits into one. @DmitryOlshansky probably will merge.

tuple( -F.infinity, int.max ),
tuple( 2.0 , 1 ),
tuple( 2.0001 , 1 ),
tuple( 1.9999 , 0 ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fix auto test failure use construction like F(1.9999) instead

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other option:

alias T = Tuple!(F, int);
T[] vals = 
[
 T(1.9999, 1),
....
];

@9il
Copy link
Member

9il commented Jul 29, 2015

@JohanEngelen You can use fixed size arrays to prevent GC allocations.

@JohanEngelen
Copy link
Contributor Author

@9il @DmitryOlshansky All done now!

@DmitryOlshansky
Copy link
Member

Going to merge in day or so unless somebody rises an issue with it.
@ibuclaw ?

@DmitryOlshansky
Copy link
Member

Hm a potential regression with this is that it was callable wirg int by implicit conversion and now it won't match the constraint.

@JohanEngelen
Copy link
Contributor Author

@DmitryOlshansky How is this solved in general in Phobos?

@9il
Copy link
Member

9il commented Aug 1, 2015

see also #3014 #3012

@DmitryOlshansky
Copy link
Member

In this case I'd add an overload for isIntegral that internally just does cast(double) for 32bit nunbers and below and cast(real) for everything else.

On the other hand - log2 is trivially implementable for integers with test and bsr.

@9il
Copy link
Member

9il commented Aug 1, 2015

The overload should be mark with deprecated, thought.

@DmitryOlshansky
Copy link
Member

@9il I'm not so sure - it's quite customary to supply integer literal for FP function expecting implicit conversion. Otherwise we'd get welcome user with hard to understand 'doesn't match overload set'.

@9il
Copy link
Member

9il commented Aug 1, 2015

@DmitryOlshansky This is bad practice that can cause out-of-the-way errors in mathematical algorithms. However, if a function can have correct and optimal implementation for integer types (like ilogb), then an integer version of this function can be added to library without deprecation.

@DmitryOlshansky
Copy link
Member

@9il I know back in the university I've seen plenty of bugs in numerics due to use of int in C programs.

@JohanEngelen
Copy link
Contributor Author

Do you want me to add a unittest explicitly testing for a few (large) integer numbers?

@DmitryOlshansky
Copy link
Member

Do you want me to add a unittest explicitly testing for a few (large) integer numbers?

Yes, integer version needs some tests to cover it.

assert(ilogb(nextUp(-float.min_normal)) == -127);
assert(ilogb(nextUp(-0.0F)) == -149);
static if (floatTraits!(real).realFormat == RealFormat.ieeeExtended) {
assert(ilogb(nextUp(-float(0.0))) == -149);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL

@JohanEngelen
Copy link
Contributor Author

@9il @DmitryOlshansky I've added implementations and tests for integer ilogb arguments. What do you think?

@DmitryOlshansky
Copy link
Member

Lookin' good

@DmitryOlshansky
Copy link
Member

Auto-merge toggled on

DmitryOlshansky added a commit that referenced this pull request Aug 2, 2015
Add a D implementation for std.math.ilogb
@DmitryOlshansky DmitryOlshansky merged commit 14301c6 into dlang:master Aug 2, 2015
JohanEngelen pushed a commit to ldc-developers/phobos that referenced this pull request Nov 9, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants