Print exact value represented by float in [0,1) Motivated by yet another floating point question on stackoverflow, I decided to print a float value \$x\in[0,1)\$, i.e., the fractional part of a float (I might try printing whole floats later). But not the rounded value like print(x) does but the exact represented value. And only using basic operations:
def print_float(x):

    # Convert float x to integer fraction num/den.
    num, den = 0, 1
    while x:
        x *= 2
        num *= 2
        den *= 2
        if x &gt;= 1:
            x -= 1
            num += 1

    # Print num/den in decimal.
    print('0.', end='')
    while num:
        num *= 10
        print(num // den, end='')
        num %= den
    print()

Testing it against a simpler way using string formatting:
from random import random

for _ in range(3):
    x = random()
    print(f'{x}:')
    print(('%.2000f' % x).rstrip('0'))
    print_float(x)
    print()

Output:
0.1659451324370237:
0.16594513243702369020837750213104300200939178466796875
0.16594513243702369020837750213104300200939178466796875

0.6127401513193578:
0.6127401513193577731186678647645749151706695556640625
0.6127401513193577731186678647645749151706695556640625

0.11658146732832175:
0.11658146732832175285210496440413407981395721435546875
0.11658146732832175285210496440413407981395721435546875
Separation of Concerns
Your function is doing too much.  It is:

Converting the float to a fraction
Converting the fraction to a string of digits
Printing the string of digits

You should separate these.
Float to Fraction
First, let's move the float-to-fraction code into its own function.
from typing import Tuple

def float_to_fraction(value: float) -&gt; Tuple[int, int]:
    &quot;&quot;&quot;
    Convert a floating point number in the range [0, 1) into a fraction.

    &gt;&gt;&gt; float_to_fraction(0.625)
    (5, 8)

    &gt;&gt;&gt; float_to_fraction(0.1)
    (3602879701896397, 36028797018963968)
    &quot;&quot;&quot;

    if not (0 &lt;= value &lt; 1):
        raise ValueError(&quot;Value out of range (0 &lt;= value &lt; 1)&quot;)
    
    numerator, denominator = 0, 1
    while value:
        value *= 2
        numerator *= 2
        denominator *= 2
        if value &gt;= 1:
            value -= 1
            numerator += 1
    return numerator, denominator

Here, I've added:

type hints, describing the input and output types for the function,
a &quot;&quot;&quot;docstring&quot;&quot;&quot; describing how to use the function,

including an example formatted for use with the doctest module,


input range validation, since your expectation is for the range to be within a narrow range of float values.

I've also changed num to numerator, since it would be easy to misinterpret the abbreviation to mean number.  Similarity, den became denominator and x became value.
Fraction to String
Again, converting a numerator/denominator fraction into a series of digits is logical unit of code, which could be reused elsewhere, so I've made it into a function:
def fraction_to_string(numerator: int, denominator: int) -&gt; str:
    &quot;&quot;&quot;
    Convert a proper fraction into a corresponding series of digits.

    &gt;&gt;&gt; fraction_to_string(5, 8)
    '0.625'
    &quot;&quot;&quot;

    if not (0 &lt;= numerator &lt; denominator):
        raise ValueError(&quot;Improper or negative fraction given&quot;)

    if denominator &amp; (denominator - 1) != 0:
        raise ValueError(&quot;Denominator must be a power of 2&quot;)

    digits = '0.'
    while numerator:
        numerator *= 10
        digits += str(numerator // denominator)
        numerator %= denominator

    return digits

Note: str(0.0) returns '0.0' but your code (and my duplication of it here) returns '0.'.
Your code originally guaranteed the denominator was a power of 2.  With this new function, a caller could ask for fraction_to_digits(1, 3) which would be an infinitely long string of digits, so I've added a condition restricting the denominator to a power of 2.
Float to String
Now we can compose these two functions, as well as recreate the original function's functionality:
def float_to_string(value: float) -&gt; str:
    numerator, denominator = float_to_fraction(value)
    return fraction_to_string(numerator, denominator)

def print_float(x):
    print(float_to_string(x))

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)

    from random import random

    for _ in range(3):
        x = random()
        print(f'{x}:')
        print(('%.2000f' % x).rstrip('0'))
        print_float(x)
        print()

Additionally, I've added a &quot;main guard&quot; (always a good idea), and I've added a call to doctest.testmod() so the tests embedded in the &quot;&quot;&quot;docstrings&quot;&quot;&quot; are executed.
Notes
The function float_to_fraction(value) may be replaced by the built-in function value.as_integer_ratio().
&gt;&gt;&gt; value = 0.625
&gt;&gt;&gt; value.as_integer_ratio()
(5, 8)