Skip to content

System.Numerics.BigInteger.TrailingZeroCount is broken for numbers with a lot of trailing zeros. #77720

Closed

Description

@tkekalainen

Description

When given a large BigInteger with a high number of trailing zeros, TrailingZeroCount only checks and counts a part of them.

Reproduction Steps

BigInteger.TrailingZeroCount(BigInteger.Pow(2, 1000))

Expected behavior

Returns 1000

Actual behavior

Returns 520

Regression?

To my knowledge, this is a new method.

Known Workarounds

None, except to avoid this method and implement the count in a slower way.

Configuration

7.0.100-rc.2.22477.23
Windows 10 21H2 (19044.2130)
x64

Other information

This loop increments i twice per iteration, once in the loop body, and a second time in the for statement:

for (int i = 1; (part == 0) && (i < value._bits.Length); i++)
{
part = value._bits[i];
result += (sizeof(uint) * 8);
i++;
}

As does this one:

for (int i = 1; (part == 0) && (i < value._bits.Length); i++)
{
// Simply process bits, adding the carry while the previous value is zero
part = ~value._bits[i] + 1;
result += (sizeof(uint) * 8);
i++;
}

This means that i = 2, 4, 6... are skipped completely, they aren't tested whether they are nonzero, nor do they increment the count.

As a side note, the bottom loop is unnecessary. The number of trailing zeros of a negative two's complement number is the same as that of its absolute value. If value._bits[i] is zero, ~value._bits[i] + 1 is also zero. And if value._bits[i] is not zero, then the end of it is of the form one, followed by n zeros. Inverting it gives zero, followed by n ones. And adding one to this changes it back to one, followed by n zeros. As we don't care about the leading bits of value._bits[i], only the number of trailing zeros, the negation again doesn't change anything.

Some C++ compilers, like clang, know this:

int trailing_zero_count(unsigned int num) { return __builtin_ctz(num); }

int trailing_zero_count_negate(unsigned int num) { return __builtin_ctz(~num + 1); }

clang 15.0.0 -O3

trailing_zero_count(unsigned int):               # @trailing_zero_count(unsigned int)
        bsf     eax, edi
        ret
trailing_zero_count_negate(unsigned int):        # @trailing_zero_count_negate(unsigned int)
        bsf     eax, edi
        ret

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Nov 1, 2022
  2. vcsjones commented on Nov 1, 2022

    @vcsjones
    Member

    I'm perplexed by the output of the answer too, but I don't expect it to be 1000. Length of the string should be 302 digits:

    Console.WriteLine(BigInteger.Pow(2, 1000).ToString().Length);

    $\lceil \log_{10} 2^{1000} \rceil$ Indeed this matches my expectations of 302. A 302-digit number having 520 trailing zeros seems unexpected.

    Returns 1000

    Can you explain how you arrived at 1000? Myself, I would expect the answer to be zero.

  3. tkekalainen commented on Nov 1, 2022

    @tkekalainen
    ContributorAuthor

    Doesn't TrailingZeroCount return the number of trailing zero bits? At least that's how it is implemented for int, uint, long etc. The documentation doesn't seem to specify, for whatever reason.

  4. vcsjones commented on Nov 1, 2022

    @vcsjones
    Member

    Doesn't TrailingZeroCount return the number of trailing zero bits?

    🤔 clearly I need more coffee.... 🤦

  5. dakersnar commented on Nov 1, 2022

    @dakersnar
    Contributor

    This is a bug. I investigated further and found that there was an accidental extra increment when iterating through the BigInteger, leading to only roughly half of the bits being counted. Putting up a fix shortly.

  6. added and removed
    untriagedNew issue has not been triaged by the area owner
    on Nov 1, 2022
  7. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Nov 1, 2022
  8. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Nov 10, 2022
  9. ghost locked as resolved and limited conversation to collaborators on Dec 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions