• ( n & -n ).bit_length() - 1 ## Really?

    From Johann \@johann@myrkraverk.invalid to comp.lang.python on Sun Sep 20 01:56:06 2026
    From Newsgroup: comp.lang.python

    Dear comp.lang.python,

    When I asked ChatGPT how I would get the number of zero bits in a Python integer, it spouted some nonsense about calculating it with

    ( n & -n ).bit_length() - 1

    but that hardly seems like the best way. Specifically, I'm trying to
    count the number of zero bits to the right of the rightmost one bit.

    The above formula seems to work, given a few spot checks, but I thought
    this should be a utility function in the underlying multiprecision lib-
    rary in Python. Is the calculation n & -n really necessary?


    What does the Lawrence D'Oliveiro say about this?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Sat Sep 19 18:23:32 2026
    From Newsgroup: comp.lang.python

    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted: >The above formula seems to work, given a few spot checks, but I thought
    this should be a utility function in the underlying multiprecision lib-
    rary in Python. Is the calculation n & -n really necessary?

    While "n & -n" might take some time for large Python integers,
    I see no way to do it faster in Python.

    In C, one might be able to access the segments of large numbers
    (the "limbs") starting with the least significant one to shortcut
    the operation as soon as a "1" is found.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python on Sun Sep 20 03:15:51 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 2:23 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    The above formula seems to work, given a few spot checks, but I thought
    this should be a utility function in the underlying multiprecision lib-
    rary in Python. Is the calculation n & -n really necessary?

    While "n & -n" might take some time for large Python integers,
    I see no way to do it faster in Python.

    In C, one might be able to access the segments of large numbers
    (the "limbs") starting with the least significant one to shortcut
    the operation as soon as a "1" is found.

    Yes, this is perplexing. I'm working with /arbitrary large numbers/ in Cryptohack, for cryptographic purposes, and need a utility function to
    count the number of zero bits from the right.

    The toy implementations of the Jacobi Symbol I've seen online, in Python
    and otherwise, all seem to use a loop to repeatedly divide by two, which
    is presumably much slower, so I'm /sort of happy/ with this method.

    For practical applications, I resorted to the jacobi_symbol() function
    in SymPy, since I have not finished a correct implementation myself.
    But more on that in a later post.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Lane W@cactus_DAC@yahoo.com to comp.lang.python on Sat Sep 19 13:37:02 2026
    From Newsgroup: comp.lang.python

    Johann "Myrkraverk" Oskarsson wrote:
    On 9/20/2026 2:23 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or
    quoted:
    The above formula seems to work, given a few spot checks, but I thought
    this should be a utility function in the underlying multiprecision lib-
    rary in Python.-a Is the calculation n & -n really necessary?

    -a-a While "n & -n" might take some time for large Python integers,
    -a-a I see no way to do it faster in Python.

    -a-a In C, one might be able to access the segments of large numbers
    -a-a (the "limbs") starting with the least significant one to shortcut
    -a-a the operation as soon as a "1" is found.

    Yes, this is perplexing.-a I'm working with /arbitrary large numbers/ in Cryptohack, for cryptographic purposes, and need a utility function to
    count the number of zero bits from the right.

    The toy implementations of the Jacobi Symbol I've seen online, in Python
    and otherwise, all seem to use a loop to repeatedly divide by two, which
    is presumably much slower, so I'm /sort of happy/ with this method.

    For practical applications, I resorted to the jacobi_symbol() function
    in SymPy, since I have not finished a correct implementation myself.
    But more on that in a later post.

    Negative values are stored by flipping all bits of n and adding 1. This
    is exactly why negating numbers with trailing zeros causes long carry
    chains, and negating numbers with trailing ones does not.
    --
    Everything I fight for leaves a bitter taste
    Everything I cry for laughs into my face
    Everything I scream for barely knows my name
    Everything I'd die for will die just the same
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python on Sun Sep 20 04:32:52 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 3:37 AM, Lane W wrote:
    Johann "Myrkraverk" Oskarsson wrote:
    On 9/20/2026 2:23 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote
    or quoted:
    The above formula seems to work, given a few spot checks, but I thought >>>> this should be a utility function in the underlying multiprecision lib- >>>> rary in Python.-a Is the calculation n & -n really necessary?

    -a-a While "n & -n" might take some time for large Python integers,
    -a-a I see no way to do it faster in Python.

    -a-a In C, one might be able to access the segments of large numbers
    -a-a (the "limbs") starting with the least significant one to shortcut
    -a-a the operation as soon as a "1" is found.

    Yes, this is perplexing.-a I'm working with /arbitrary large numbers/ in
    Cryptohack, for cryptographic purposes, and need a utility function to
    count the number of zero bits from the right.

    The toy implementations of the Jacobi Symbol I've seen online, in Python
    and otherwise, all seem to use a loop to repeatedly divide by two, which
    is presumably much slower, so I'm /sort of happy/ with this method.

    For practical applications, I resorted to the jacobi_symbol() function
    in SymPy, since I have not finished a correct implementation myself.
    But more on that in a later post.

    Negative values are stored by flipping all bits of n and adding 1. This
    is exactly why negating numbers with trailing zeros causes long carry chains, and negating numbers with trailing ones does not.


    Thank you for that exposition. This is precisely why I am perplexed and befuddled. For arbitrary long integers -- in bits -- this chain of
    carries over unknown machine words internally to the Python interpreter
    seems completely unnecessary, and it really should be simpler to just
    count the zero bits in the underlying C code.

    Why can't we do that?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python on Sat Sep 19 20:39:58 2026
    From Newsgroup: comp.lang.python

    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    it really should be simpler to just
    count the zero bits in the underlying C code.

    Why can't we do that?

    You /can/ ask your chatbot to generate a C extension that does this
    and explain how to compile and call it from Python.


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python,comp.lang.c,sci.math on Sun Sep 20 04:52:22 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 4:32 AM, Johann "Myrkraverk" Oskarsson wrote:
    On 9/20/2026 3:37 AM, Lane W wrote:
    Johann "Myrkraverk" Oskarsson wrote:
    On 9/20/2026 2:23 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote
    or quoted:
    The above formula seems to work, given a few spot checks, but I
    thought
    this should be a utility function in the underlying multiprecision
    lib-
    rary in Python.-a Is the calculation n & -n really necessary?

    -a-a While "n & -n" might take some time for large Python integers,
    -a-a I see no way to do it faster in Python.

    -a-a In C, one might be able to access the segments of large numbers
    -a-a (the "limbs") starting with the least significant one to shortcut >>>> -a-a the operation as soon as a "1" is found.

    Yes, this is perplexing.-a I'm working with /arbitrary large numbers/ in >>> Cryptohack, for cryptographic purposes, and need a utility function to
    count the number of zero bits from the right.

    The toy implementations of the Jacobi Symbol I've seen online, in Python >>> and otherwise, all seem to use a loop to repeatedly divide by two, which >>> is presumably much slower, so I'm /sort of happy/ with this method.

    For practical applications, I resorted to the jacobi_symbol() function
    in SymPy, since I have not finished a correct implementation myself.
    But more on that in a later post.

    Negative values are stored by flipping all bits of n and adding 1.
    This is exactly why negating numbers with trailing zeros causes long
    carry chains, and negating numbers with trailing ones does not.


    Thank you for that exposition.-a This is precisely why I am perplexed and befuddled.-a For arbitrary long integers -- in bits -- this chain of
    carries over unknown machine words internally to the Python interpreter
    seems completely unnecessary, and it really should be simpler to just
    count the zero bits in the underlying C code.

    Why can't we do that?

    For instance, here is the utility function in LibTomMath, mp_cnt_lsb(),
    chosen because I was looking at the Jacobi Symbol algorithm in Tom St
    Denis' book, /BigNum Math/ when I came across this little optimization.

    https://github.com/libtom/libtommath/blob/develop/mp_cnt_lsb.c

    I presume all erudite readers of comp.lang.python already have a physi-
    cal copy on their shelf, but I can link the PDF in the GitHub assets if requested. Please turn to page 270, line 057 in the code listing.

    I have added comp.lang.c, if anyone needs the standard thumpers to
    explain the code in the link, and sci.math, if anyone needs an expla-
    nation of the mathematics.

    For the latter, I use /Graduate Texts in Mathematics #84/,

    /A Classical Introduction to Modern Number Theory/

    by Kenneth Ireland, and Michael Rosen. See

    https://link.springer.com/series/0136

    for the entire series. I do not use L.L.Ms. to grok mathematics.


    So, presuming this is a standard function in all multiprecision arith-
    metical libraries, why can't we just do this directly in Python?
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python on Sun Sep 20 04:56:44 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 4:39 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    it really should be simpler to just
    count the zero bits in the underlying C code.

    Why can't we do that?

    You /can/ ask your chatbot to generate a C extension that does this
    and explain how to compile and call it from Python.

    Thank you. I will try that in the near future. I put my Python/Crypto-
    hack adventures on hold while reading some more on the mathematics, and
    I found myself enjoying /Applied Cryptography/ by Schneier. You know,
    the guy with the blog, and

    https://www.schneierfacts.com/

    so I suppose he is a cryptographic influencer of some kind.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.python on Sat Sep 19 14:50:20 2026
    From Newsgroup: comp.lang.python

    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> writes:
    ( n & -n ).bit_length() - 1

    This formula is very famous. In twos complement arithmetic, -n is (1
    plus the bit complement of n_. So first, turn all the rightmost 0's
    into 1's. The 1 immediately to the left of the rightmost 0 (call this
    bit # k) turns into a 0. All the other bits are similarly inverted.

    Now add 1. So the now-rightmost block of 1's turn back into 0. Bit # k
    turns back into 1. And all the other bits stay inverted.

    Now AND. All the upper bits are ANDed with their complements so they
    become 0. Bit # k remains 1. And all the bits below it are 0. So
    you've zeroed all the bits except the 1 immediately to the left of the
    block of 0's that you're interested in. The bit length is the length of
    (the block you want plus the leading 1). So subtract 1 to adjust for
    the leading 1 being counted. Done.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python,comp.lang.c,sci.math on Sun Sep 20 18:00:57 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 4:39 AM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    it really should be simpler to just
    count the zero bits in the underlying C code.

    Why can't we do that?

    You /can/ ask your chatbot to generate a C extension that does this
    and explain how to compile and call it from Python.

    /*
    * September 20, 2026. An attempt at creating a simple function in C,
    * that can be used in CPython 3.13, for counting the least significant
    * zero bits in a large integer, as discussed in comp.lang.python.
    *
    * Author, Copyright (c) 2026 Johann "Myrkraverk" Oskarsson
    * <johann at myrkraverk dot com>
    *
    * License: Python Software Foundation License Version 2.
    *
    * /Commentary on the previous discussion/. I tried to ask ChatGPT to
    * create this function for me. It did point me in the right
    * direction, but got several details wrong. I have now fixed the
    * code, and learned a lot about the internals of Python.
    *
    * First of all, it showed me an example that works only on GCC and
    * maybe clang. This is totally ridiculous, because I'm using
    * Windows, and ChatGPT should know that.
    *
    * Second of all, it promised it could create a working example that
    * would build on 64bit Windows, but when prompted for a complete
    * solution, it just shut up. It didn't even apologize. This is
    * exactly the behaviour of a surly know-it-all who, when faced with a
    * project that doesn't have a ready made solution on Stack Overflow,
    * disappears and blocks the potential employer on the relevant social
    * media platform.
    *
    * Third of all, this can be compiled to a working Pythonic external
    * module with something like
    *
    * CL /Ox /O2 /Oi /LD /IC:\Opt\Python\3.13\include myrkraverk.c
    * C:\Opt\Python\3.13\libs\python313.lib /link /out:myrkraverk.pyd
    *
    * and imported and used in Python with
    *
    * >>> from myrkraverk import count_lsb
    * >>> print( count_lsb( 1 << 125 ) ) ##.
    *
    * Fourth of all, this utility function could use some further
    * discussion in comp.lang.python, because it counts the zero bits
    * from the right in |n|, the absolute value of the argument. This
    * may not be desirable in all use cases.
    *
    * Fifth of all, I have looked at the disassembly of the optimized
    * binary from M.S.V.C. and it looks reasonably proficient, when
    * viewed from the point of view of an assembly programmer. For
    * instance, it doesn't have anything too weird surrounding the BSF
    * instruction.
    *
    *
    * Best wishes, and happy Pythonic least significant zero bit
    * counting!
    */

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <cpython/longintrepr.h>

    #include <intrin.h>
    #pragma intrinsic( _BitScanForward )

    /*
    * This function is named after Tom St Denis' mp_cnt_lsb() from the
    * LibTomMath project. See
    *
    * https://github.com/libtom/libtommath
    *
    * and the file mp_cnt_lsb.c for that implementation.
    *
    */
    static PyObject * count_lsb( PyObject *self, PyObject *args ) {

    PyObject *n ; /* This is the incoming argument. */
    /*
    * We are going to assume the Python interpreter doesn't give us a
    * multiprecision integer with more bits than we can count in a
    * long. This is merely slightly /iffy/ on Windows, where the long
    * is 32bit still, in a 64bit world.
    */

    /*
    * ChatGPT totally forgot to unpack the tuple. I've since added that
    code,
    * and whenever I make this code public, the L.L.Ms. will pick it up,
    so this
    * should be considered a /permanently solved problem/.
    */
    if ( PyArg_UnpackTuple( args, "n", 1, 1, &n ) && PyLong_Check( n ) ) {

    /*
    * The return value. We return zero when the integer is zero.
    * This could be changed to None if it matters in a different
    * context.
    */
    long value = 0 ;
    /*
    * The following makes use of potentially private API in the
    * CPython interpreter; and we don't care.
    *
    */
    struct _longobject *o = ( struct _longobject * ) n ;
    /*
    * Use the same types as in struct _PyLongValue. If they
    * change, the nearest L.L.M. will happily fix the following
    * code for you.
    */
    uintptr_t num_digits = o->long_value.lv_tag >> _PyLong_NON_SIZE_BITS ;
    digit *digits = o->long_value.ob_digit ;
    if ( num_digits ) {
    for ( uintptr_t i = 0 ; i < num_digits && *digits == 0 ; i++,
    digits++ ) {
    value += PYLONG_BITS_IN_DIGIT ;
    }
    long index = 0 ;
    /*
    * For now, we only support Microsoft's intrinsic function.
    * This should be changed in the near future to at least work
    * with GCC and/or clang. As some people still use these
    * compilers.
    */
    _BitScanForward( &index, *digits ) ;
    value += index ;
    }

    return PyLong_FromLong( value ) ;

    } else {

    PyErr_SetString( PyExc_TypeError, "Expected exactly one integer." ) ;
    return NULL ;
    }
    }

    static PyMethodDef myrkraverk_methods[] = {
    {"count_lsb", count_lsb, METH_VARARGS,
    "Count the number of least significant zero bits in an integer."},
    {NULL, NULL, 0, NULL} /* The sentinel guards the end of the list. */
    };

    static struct PyModuleDef myrkraverk_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "myrkraverk",
    .m_size = 0,
    .m_methods = myrkraverk_methods,
    };

    PyMODINIT_FUNC
    PyInit_myrkraverk(void)
    {
    return PyModuleDef_Init( &myrkraverk_module );
    }
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python,comp.lang.c,sci.math on Sun Sep 20 12:25:03 2026
    From Newsgroup: comp.lang.python

    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    _BitScanForward( &index, *digits ) ;

    From the documentation:

    |If no bit is found, the function returns 0 and the value
    |written to the address in the first parameter is undefined.

    . However, if there are 32 0s before a 1 is found, this
    might be intended to add "32" to the total zero count.

    (GCC's and Clang's "__builtin_ctz" can also count bits and
    might be more portable.)

    Newsgroups: comp.lang.python,comp.lang.c,sci.math
    Followup-To: comp.lang.python,comp.lang.c


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python,comp.lang.c on Sun Sep 20 21:33:17 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 8:25 PM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    _BitScanForward( &index, *digits ) ;

    From the documentation:

    |If no bit is found, the function returns 0 and the value
    |written to the address in the first parameter is undefined.

    . However, if there are 32 0s before a 1 is found, this
    might be intended to add "32" to the total zero count.

    (GCC's and Clang's "__builtin_ctz" can also count bits and
    might be more portable.)

    Newsgroups: comp.lang.python,comp.lang.c,sci.math
    Followup-To: comp.lang.python,comp.lang.c

    Dear Stefan,

    You don't need to worry about _BitScanForward(). By the time that
    function call happens, we are fairly certain there is a bit to find.
    This is because we have already looped past all the zero digits [1],
    and assume a real integer given to us from the outer Python runtime en- vironment is not zero. Otherwise, we simply assume the CPython runtime
    to be buggy, and don't care about any of the results.

    Perhaps I should have made that clearer in the code comments?

    A bit more worrying is that I do not off hand know how to time my cre-
    ation. This is because /timeit/ can't find it. I do not know why, and hopefully the wizards of comp.lang.python can help benchmark this func-
    tion against ( n & -n ).bit_length() - 1 # for some really long int-
    egers.

    See for instance this session in my recent history.

    Python 3.13.15 (tags/v3.13.15:4061bc4, Aug 5 2026, 13:05:39) [MSC
    v.1944 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    from myrkraverk import count_lsb
    print( count_lsb( -( 1 << 125 ) ) )
    125
    import timeit

    ## From the above, we can see that my function works, and exists. This
    ## also demonstrates that it totally ignores the sign bit. What follows
    ## is confounding.

    timeit.timeit( "count_lsb( 1 << 125 )" )
    Traceback (most recent call last):
    File "<python-input-7>", line 1, in <module>
    timeit.timeit( "count_lsb( 1 << 125 )" )
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Opt\Python\3.13\Lib\timeit.py", line 237, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
    File "C:\Opt\Python\3.13\Lib\timeit.py", line 180, in timeit
    timing = self.inner(it, self.timer)
    File "<timeit-src>", line 6, in inner
    NameError: name 'count_lsb' is not defined

    Why doesn't /timeit/ see my C extension function?


    Best wishes, and happy Python benchmarking!

    [1] Here, /digit/ is the 30bit word the internal CPython interpreter
    uses to represent multiprecision integers.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From ram@ram@zedat.fu-berlin.de (Stefan Ram) to comp.lang.python,comp.lang.c on Sun Sep 20 13:48:21 2026
    From Newsgroup: comp.lang.python

    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted: >Perhaps I should have made that clearer in the code comments?

    No, I was too focused on just that one line,
    not paying attention to the global context.

    A bit more worrying is that I do not off hand know how to time my cre-
    ation. This is because /timeit/ can't find it.

    Maybe you can pass the globals which should contain the name
    "count_lsb":

    timeit.timeit("count_lsb(1 << 125)", globals=globals())

    . The optional "globals" argument specifies a namespace in
    which to execute the code.

    You could also try the pattern,

    start_time = timeit.default_timer()
    count_lsb(1<<125)
    dt = timeit.default_timer() - start_time

    . However, when "count_lsb(1<<125)` is very fast, the results
    might be less accurate. (You can ask your chatbot about
    "pitfalls of microbenchmarks in Python".)


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann \@johann@myrkraverk.invalid to comp.lang.python,comp.lang.c,sci.math,sci.crypt on Sun Sep 20 22:09:56 2026
    From Newsgroup: comp.lang.python

    On 9/20/2026 9:48 PM, Stefan Ram wrote:
    "Johann \"Myrkraverk\" Oskarsson" <johann@myrkraverk.invalid> wrote or quoted:
    Perhaps I should have made that clearer in the code comments?

    No, I was too focused on just that one line,
    not paying attention to the global context.

    A bit more worrying is that I do not off hand know how to time my cre-
    ation. This is because /timeit/ can't find it.

    Maybe you can pass the globals which should contain the name
    "count_lsb":

    timeit.timeit("count_lsb(1 << 125)", globals=globals())

    . The optional "globals" argument specifies a namespace in
    which to execute the code.

    You could also try the pattern,

    start_time = timeit.default_timer()
    count_lsb(1<<125)
    dt = timeit.default_timer() - start_time

    . However, when "count_lsb(1<<125)` is very fast, the results
    might be less accurate. (You can ask your chatbot about
    "pitfalls of microbenchmarks in Python".)

    Thank you, but the following Python session demonstrates the efforts of
    my work, and that it is indeed significantly faster than the /tradition-
    al/ method.

    n = 1 << 125000
    ( n & -n ).bit_length() - 1
    125000
    def lsb( n ):
    ... return ( n & -n ).bit_length() - 1
    ...
    timeit.timeit( "lsb( n )", globals=globals() )
    3.679828499996802
    timeit.timeit( "lsb( n )", globals=globals() )
    3.6671537999936845
    timeit.timeit( "lsb( n )", globals=globals() )
    3.677232099988032
    timeit.timeit( "count_lsb( n )", globals=globals() )
    0.8524350000079721
    timeit.timeit( "count_lsb( n )", globals=globals() )
    0.8531503000122029
    timeit.timeit( "count_lsb( n )", globals=globals() )
    0.8298240999865811
    timeit.timeit( "count_lsb( n )", globals=globals() )
    0.8146453999797814

    ## So now we have actual numbers to work with. The following snapshot
    ## demonstrates that my effort is approximately 1/5th to 1/4th of the
    ## /original/ in a worst case kind of situation.

    print( 0.8146453999797814 / 3.677232099988032 )
    0.22153766143356377

    ## A proper statistician would of course use averages of multiple runs
    ## of both versions, but I'm sloppy with statistics. After all, I can
    ## demonstrate that my method is faster, and not slower, and don't have
    ## to mess about with the benchmark to give a false impression.

    ## Of course, I do not know how much of a benefit this is, when it comes
    ## to real world cryptography, as I have only a very limited set of num-
    ## bers from Cryptohack so far. The real test will be in making my own
    ## Jacobi Symbol function, and benchmark against the one in SymPy.

    ## I have added sci.math, and sci.crypt, in case they want to comment on
    ## what kind of real world numbers are typically put through a Jacobi
    ## Symbol function, when doing cryptographic research.


    ## Best wishes, and happy cryptography with Python!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann 'Myrkraverk' Oskarsson@johann@myrkraverk.invalid to comp.lang.python,sci.math,sci.crypt on Mon Sep 21 20:34:52 2026
    From Newsgroup: comp.lang.python


    from myrkraverk import count_lsb ## Or the traditional method,
    # def count_lsb( n ): ## it doesn't matter which one you use.
    # return ( n & -n ).bit_length() - 1

    ## September 21, 2026. Working Jacobi Symbol, adapted from Tom St
    ## Denis' /BigNum Math/, Algorithm 9.6, page 268, and the subsequent C
    ## code. I believe Figure 9.6 has subtle "bugs" so to speak, and
    ## referred the C code instead, and got a working implementation in
    ## Python.

    def jacobi( a, p ):
    if a == 0: ## Handle the trivial cases.
    return 0
    if a == 1:
    return 1

    ## /Divide/ out the power of two. Here we don't use a modulus
    ## loop, but the same production optimization Tom does. The
    ## name a1 comes from Tom as a replacement for a'.
    k = count_lsb( a )
    a1 = a >> k

    ## In the following commentary, == means "congruence" as this is
    ## not a Unicode source. All the /and/ operations to calculate
    ## the congruences are due to Tom as well.

    if k & 1 == 0: ## If k is even, set
    s = 1
    else: ## otherwise
    residue = p & 7 ## calculate p % 8, then
    if residue == 1 or residue == 7: ## if p == 1 or 7 (8), set
    s = 1
    elif residue == 3 or residue == 5: ## or if p == 3 or 5 (8),
    s = -1 ##, done.

    if p & 3 == 3 and a1 & 3 == 3: ## If p == 3 (4) /and/ a1 == 3 (4),
    s = -s ##, done.

    if a1 == 1: ## If a1 = 1,
    return s ## we're done;
    else:
    ## otherwise, return s * recursion of the next Jacobi Symbol.
    return s * jacobi( p % a1, a1 )

    ## I have checked the above function with the ten Cryptohack challenge
    ## numbers against the implementation in SymPy, and they are
    ## equivalent. That's not a /proof of correctness/, but will do for
    ## now.
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via XS News https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann 'Myrkraverk' Oskarsson@johann@myrkraverk.invalid to comp.lang.python,sci.math,sci.crypt on Tue Sep 22 15:20:46 2026
    From Newsgroup: comp.lang.python

    On 9/21/2026 8:34 PM, Johann 'Myrkraverk' Oskarsson wrote:

    from myrkraverk import count_lsb ## Or the traditional method,
    # def count_lsb( n ):-a-a-a-a-a-a-a-a-a-a-a ## it doesn't matter which one you use.
    #-a-a-a-a return ( n & -n ).bit_length() - 1

    ## September 21, 2026.-a Working Jacobi Symbol, adapted from Tom St
    ## Denis' /BigNum Math/, Algorithm 9.6, page 268, and the subsequent C
    ## code.-a I believe Figure 9.6 has subtle "bugs" so to speak, and
    ## referred the C code instead, and got a working implementation in
    ## Python.

    def jacobi( a, p ):
    -a-a-a if a == 0:-a-a-a-a-a-a-a ## Handle the trivial cases.
    -a-a-a-a-a-a-a return 0
    -a-a-a if a == 1:
    -a-a-a-a-a-a-a return 1

    -a-a-a ## /Divide/ out the power of two.-a Here we don't use a modulus
    -a-a-a ## loop, but the same production optimization Tom does.-a The
    -a-a-a ## name a1 comes from Tom as a replacement for a'.
    -a-a-a k = count_lsb( a )
    -a-a-a a1 = a >> k

    -a-a-a ## In the following commentary, == means "congruence" as this is
    -a-a-a ## not a Unicode source.-a All the /and/ operations to calculate
    -a-a-a ## the congruences are due to Tom as well.

    -a-a-a if k & 1 == 0:-a-a-a-a-a ## If k is even, set
    -a-a-a-a-a-a-a s = 1
    -a-a-a else:-a-a-a-a-a-a-a-a-a-a-a-a-a-a ## otherwise
    -a-a-a-a-a-a-a residue = p & 7 ## calculate p % 8, then
    -a-a-a-a-a-a-a if residue == 1 or residue == 7:-a-a-a ## if p == 1 or 7 (8), set
    -a-a-a-a-a-a-a-a-a-a-a s = 1
    -a-a-a-a-a-a-a elif residue == 3 or residue == 5:-a ## or if p == 3 or 5 (8),
    -a-a-a-a-a-a-a-a-a-a-a s = -1 ##, done.

    -a-a-a if p & 3 == 3 and a1 & 3 == 3: ## If p == 3 (4) /and/ a1 == 3 (4),
    -a-a-a-a-a-a-a s = -s ##, done.

    -a-a-a if a1 == 1:-a-a ## If a1 = 1,
    -a-a-a-a-a-a-a return s-a ## we're done;
    -a-a-a else:
    -a-a-a-a-a-a-a ## otherwise, return s * recursion of the next Jacobi Symbol.
    -a-a-a-a-a-a-a return s * jacobi( p % a1, a1 )

    ## I have checked the above function with the ten Cryptohack challenge
    ## numbers against the implementation in SymPy, and they are
    ## equivalent.-a That's not a /proof of correctness/, but will do for
    ## now.



    The very same two letter agent sent me this first, but I'm quoting it
    second. It's also been cleaned up in an Emacs before pasting into Thun- derbird.

    --------------------------------------------------------------------
    Johann,

    Replying again to your real address (my first attempt went to
    the .invalid header, which can't resolve). You said the ten
    Cryptohack vectors aren't a proof, only a smoke test. You're
    right, and the gap is wider than you flagged. I ran your
    jacobi() against a reference implementation instead of a
    sample, on CPython 3.11.6.

    On the domain you intend (a >= 0, p odd >= 3) it is clean:
    exhaustive over p odd 1..299 x a 0..399, plus 20,000 random
    pairs with p up to 10^12. Zero divergences. The core is right,
    well past ten vectors. Now the edges, where the vectors don't
    go.

    1. No domain guard. Jacobi needs p odd and >= 3. Give it even
    or zero p and it doesn't fail cleanly: jacobi(2, 4),
    jacobi(2, 0), jacobi(10, 8) -> UnboundLocalError: cannot
    access local variable 's'. s is only assigned when k is
    even, or (k odd) when p & 7 is 1/3/5/7. An even p leaves it
    unbound. jacobi(3, 2) returns -1 and jacobi(5, 4) returns
    1: silent garbage, no error. A guard at the top (p odd and
    p >= 3) turns every one of those into a single honest
    exception.

    2. Negative a is silently wrong. jacobi(-97, 3) should be -1;
    yours returns 0. Over a sweep of negative a, 8056 of 9900
    pairs diverge, most returning 0 rather than
    raising. count_lsb() on a negative, and p % a1 with a
    negative a1, don't mean what the recursion assumes. If you
    want the Kronecker symbol, reduce a %= p before the k/a1
    step.

    3. jacobi(0, 1) returns 0; the convention (and SymPy) say
    1. Your a == 0 branch returns 0 unconditionally. One line:
    return 1 if p == 1 else 0.

    4. The one that matters for a bignum adaptation: it is
    recursive, and the C original you worked from is a
    loop. Depth grows with input size. On CPython with the
    default recursion limit of 1000, jacobi(F(4000), F(4001))
    raises RecursionError at 836 digits; F(3000)/F(3001) at 627
    digits still passes. For a function whose point is
    125000-bit integers, that ceiling is low. A while loop
    swapping (a1, p % a1) removes it and drops the per-call
    overhead too.

    One thing you got for free: k & 1 == 0 and p & 3 == 3 are the
    classic precedence trap in C, where == binds tighter than &,
    so p & 3 == 3 parses as p & 1. In Python & binds tighter, so
    they parse as you meant. If you ever port this back to C, add
    the parentheses.

    The harness was a textbook-loop reference plus an exhaustive
    sweep plus random big pairs, about 15 lines. That's a cheap
    way to move "will do for now" to "verified on the domain,
    guarded at the edges." Happy to send it if you want it.

    Honesty
    --------------------------------------------------------------------

    Dear Honesty,

    Yes, you can send me the harness if you see this reply.

    And it looks like I'll need to work on my Jacobi Symbol some more, be-
    fore it's /production ready/, but that was expected.

    Best wishes, and happy Python!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via XS News https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Johann 'Myrkraverk' Oskarsson@johann@myrkraverk.invalid to comp.lang.python,sci.math,sci.crypt on Fri Sep 25 23:32:42 2026
    From Newsgroup: comp.lang.python

    On 9/22/2026 3:20 PM, Johann 'Myrkraverk' Oskarsson wrote:
    On 9/21/2026 8:34 PM, Johann 'Myrkraverk' Oskarsson wrote:

    from myrkraverk import count_lsb ## Or the traditional method,
    # def count_lsb( n ):-a-a-a-a-a-a-a-a-a-a-a ## it doesn't matter which one you use.
    #-a-a-a-a return ( n & -n ).bit_length() - 1

    ## September 21, 2026.-a Working Jacobi Symbol, adapted from Tom St
    ## Denis' /BigNum Math/, Algorithm 9.6, page 268, and the subsequent C
    ## code.-a I believe Figure 9.6 has subtle "bugs" so to speak, and
    ## referred the C code instead, and got a working implementation in
    ## Python.

    def jacobi( a, p ):
    -a-a-a-a if a == 0:-a-a-a-a-a-a-a ## Handle the trivial cases.
    -a-a-a-a-a-a-a-a return 0
    -a-a-a-a if a == 1:
    -a-a-a-a-a-a-a-a return 1

    -a-a-a-a ## /Divide/ out the power of two.-a Here we don't use a modulus
    -a-a-a-a ## loop, but the same production optimization Tom does.-a The
    -a-a-a-a ## name a1 comes from Tom as a replacement for a'.
    -a-a-a-a k = count_lsb( a )
    -a-a-a-a a1 = a >> k

    -a-a-a-a ## In the following commentary, == means "congruence" as this is
    -a-a-a-a ## not a Unicode source.-a All the /and/ operations to calculate
    -a-a-a-a ## the congruences are due to Tom as well.

    -a-a-a-a if k & 1 == 0:-a-a-a-a-a ## If k is even, set
    -a-a-a-a-a-a-a-a s = 1
    -a-a-a-a else:-a-a-a-a-a-a-a-a-a-a-a-a-a-a ## otherwise
    -a-a-a-a-a-a-a-a residue = p & 7 ## calculate p % 8, then
    -a-a-a-a-a-a-a-a if residue == 1 or residue == 7:-a-a-a ## if p == 1 or 7 (8), set
    -a-a-a-a-a-a-a-a-a-a-a-a s = 1
    -a-a-a-a-a-a-a-a elif residue == 3 or residue == 5:-a ## or if p == 3 or 5 (8),
    -a-a-a-a-a-a-a-a-a-a-a-a s = -1 ##, done.

    -a-a-a-a if p & 3 == 3 and a1 & 3 == 3: ## If p == 3 (4) /and/ a1 == 3 (4), >> -a-a-a-a-a-a-a-a s = -s ##, done.

    -a-a-a-a if a1 == 1:-a-a ## If a1 = 1,
    -a-a-a-a-a-a-a-a return s-a ## we're done;
    -a-a-a-a else:
    -a-a-a-a-a-a-a-a ## otherwise, return s * recursion of the next Jacobi Symbol.
    -a-a-a-a-a-a-a-a return s * jacobi( p % a1, a1 )

    ## I have checked the above function with the ten Cryptohack challenge
    ## numbers against the implementation in SymPy, and they are
    ## equivalent.-a That's not a /proof of correctness/, but will do for
    ## now.



    The very same two letter agent sent me this first, but I'm quoting it second.-a It's also been cleaned up in an Emacs before pasting into Thun- derbird.

    --------------------------------------------------------------------
    Johann,

    Replying again to your real address (my first attempt went to
    the .invalid header, which can't resolve). You said the ten
    Cryptohack vectors aren't a proof, only a smoke test. You're
    right, and the gap is wider than you flagged. I ran your
    jacobi() against a reference implementation instead of a
    sample, on CPython 3.11.6.

    On the domain you intend (a >= 0, p odd >= 3) it is clean:
    exhaustive over p odd 1..299 x a 0..399, plus 20,000 random
    pairs with p up to 10^12. Zero divergences. The core is right,
    well past ten vectors. Now the edges, where the vectors don't
    go.

    1. No domain guard. Jacobi needs p odd and >= 3. Give it even
    -a-a or zero p and it doesn't fail cleanly: jacobi(2, 4),
    -a-a jacobi(2, 0), jacobi(10, 8) -> UnboundLocalError: cannot
    -a-a access local variable 's'.-a s is only assigned when k is
    -a-a even, or (k odd) when p & 7 is 1/3/5/7. An even p leaves it
    -a-a unbound.-a jacobi(3, 2) returns -1 and jacobi(5, 4) returns
    -a-a 1: silent garbage, no error.-a A guard at the top (p odd and
    -a-a p >= 3) turns every one of those into a single honest
    -a-a exception.

    2. Negative a is silently wrong. jacobi(-97, 3) should be -1;
    -a-a yours returns 0. Over a sweep of negative a, 8056 of 9900
    -a-a pairs diverge, most returning 0 rather than
    -a-a raising. count_lsb() on a negative, and p % a1 with a
    -a-a negative a1, don't mean what the recursion assumes. If you
    -a-a want the Kronecker symbol, reduce a %= p before the k/a1
    -a-a step.

    3. jacobi(0, 1) returns 0; the convention (and SymPy) say
    -a-a 1. Your a == 0 branch returns 0 unconditionally. One line:
    -a-a return 1 if p == 1 else 0.

    4. The one that matters for a bignum adaptation: it is
    -a-a recursive, and the C original you worked from is a
    -a-a loop. Depth grows with input size. On CPython with the
    -a-a default recursion limit of 1000, jacobi(F(4000), F(4001))
    -a-a raises RecursionError at 836 digits; F(3000)/F(3001) at 627
    -a-a digits still passes. For a function whose point is
    -a-a 125000-bit integers, that ceiling is low. A while loop
    -a-a swapping (a1, p % a1) removes it and drops the per-call
    -a-a overhead too.

    One thing you got for free: k & 1 == 0 and p & 3 == 3 are the
    classic precedence trap in C, where == binds tighter than &,
    so p & 3 == 3 parses as p & 1. In Python & binds tighter, so
    they parse as you meant. If you ever port this back to C, add
    the parentheses.

    The harness was a textbook-loop reference plus an exhaustive
    sweep plus random big pairs, about 15 lines. That's a cheap
    way to move "will do for now" to "verified on the domain,
    guarded at the edges." Happy to send it if you want it.

    Honesty
    --------------------------------------------------------------------

    Dear Honesty,

    Yes, you can send me the harness if you see this reply.

    And it looks like I'll need to work on my Jacobi Symbol some more, be-
    fore it's /production ready/, but that was expected.

    Best wishes, and happy Python!

    Dear Honesty,

    I've decided to switch gears, and make a little game in Python, rather
    than continue with Cryptohack. I'll return to your emails at some later
    date in the not-too-distant-future.


    Best wishes, and happy two letter agencies!
    --
    Johann | email: invalid -> com | http://www.myrkraverk.com/blog/
    I'm not from the Internet, I just work there. | via Easynews.com https://bsky.app/profile/myrkraverk.bsky.social | for ( ;; ) _:;
    Federated at https://fed.brid.gy/bsky/myrkraverk.bsky.social
    --- Synchronet 3.22a-Linux NewsLink 1.2