添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Stack Exchange Network

Stack Exchange network consists of 183 Q&A communities including Stack Overflow , the largest, most trusted online community for developers to learn, share their knowledge, and build their careers. Visit Stack Exchange

Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It only takes a minute to sign up.

Sign up to join this community

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

This is a Python 3 script that converts any real number ( int s and float s) from decimal notation to any positional number system representation with an integer base between 2 and 36 and vice versa, meaning it can convert negative fractions.

Technically it can convert any arbitrary base, but I struggle to find a way to represent the output, its output is like hexadecimal, but instead of only letters a to f, all of the English alphabet can be used, so there can be a unique symbol for all numbers up to 35.

I don't know how to represent number systems with bases higher than 36, if I continue to use strings, if I use the Greek alphabet after Latin alphabet, I can represent bases up to 60, but many Greek letters have homoglyph in Latin alphabet... And if I just use a list , it would be perfectly fine if they only represent positive integers, but here I need a list for the integral part and a list for the fractional part, and potentially a str before the first list ...

The actual code that does the job is extremely simple, but I added many useless validations that slows the execution down tremendously, just so in case someone gives incorrect inputs...

import re
from typing import Union
ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
def convertfrom_decimal(number: Union[int, float], base: int=16) -> str:
    if not isinstance(number, (float, int)):
        raise TypeError('Argument `number` must be a numerical value (int or float)')
    if not isinstance(base, int):
        raise TypeError('Argument `base` must be an integer')
    if not 2 <= base <= 36:
        raise ValueError('This function can only support bases from 2 to 36')
    negative = False
    if number < 0:
        negative = True
        number = -number
    whole = int(number)
    fraction = number - whole
    if whole:
        integral = ''
        while whole:
            whole, digit = divmod(whole, base)
            integral = ALPHABET[digit] + integral
    else:
        integral = '0'
    if not fraction:
        return integral if not negative else '-'+integral
    fractional = ''
    bits = 0
    while bits < 48:
        bit, fraction = divmod(fraction*base, 1.0)
        fractional += ALPHABET[int(bit)]
        if fraction < 2**-48:
            break
        bits += 1
    result = integral + '.' + fractional
    return result if not negative else '-'+result
def convertto_decimal(string: str, base: int=0) -> float:
    if not isinstance(string, str):
        raise TypeError('Argument `string` must be an instance of `str`')
    if not isinstance(base, int):
        raise TypeError('Argument `base` must be an instance of `int`')
    if base and not 2 <= base <= 36:
        raise ValueError('Argument `base` is not between 2 and 36')
    if not re.match('^(\-|\+)?[0-9a-z]+(\.[0-9a-z]+)?$', string):
        raise ValueError('Argument string is not a valid numerical notation supported by this function')
    negative = False
    if string[0] == '-':
        negative = True
        string = string[1:]
    fraction = ''
    if '.' in string:
        whole, fraction = string.split('.')
    else:
        whole = string
    indicator = max(ALPHABET.index(d) for d in string if d in ALPHABET)
    if not base:
        for b in (2, 8, 16, 36):
            if indicator < b:
                base = b
                break
    if indicator >= base:
        raise ValueError('Argument `base` is incorrect')
    #integral = int(whole, base)
    integral = sum(
        ALPHABET.index(d)*base**i for i, d in enumerate(whole[::-1])
    if not fraction:
        return integral if not negative else -integral
    fractional = sum(
        ALPHABET.index(d)*base**-(i+1) for i, d in enumerate(fraction)
    number = integral + fractional
    return number if not negative else -number
if __name__ == '__main__':
    for i in range(2, 37):                               
        print(convertfrom_decimal(3.1415926535897932, i))

Output

11.001001000011111101101010100010001000010110100011
10.010211012222010211002111110221222020010102220122
3.021003331222202020112203
3.032322143033432411241211414143234130344233124014
3.050330051415124105232005511454424522431000231043
3.066365143203613410601052256200101122510662105012
3.1103755242102643
3.124188124074427866112818683125147474717151652050
3.141592653589793115997963468544185161590576171875
3.16150702865a484776333a98347444320a009a7420206870
3.184809493b9186459aaa3a83
3.1ac1049052a2c71005161571824ba0969c9c2497709caba9
3.1da75cda813752b70268a34a22a74bc41348ccb8c5b228a7
3.21cd1dc46c2b7ab624ee5cd3a5322906081b7e4cc8822538
3.243f6a8885a3
3.26fag579ed6gdea8g2f5a1a2386be4847dfa9c199955365a
3.29fdeh0g77186b7e590fg494559fgf1df946f3b06h5636d9
3.2d23982975gfh9b5f957e5005e7c16d3dg23bh47838i4h7i
3.2gceg9gbhj9cc1508a2e3jdf
3.2k961edi5h85d7fhkhd2idf09bjkafe8bf0513def9k2hkj3
3.32bek9a809gafkj34f0jlilchkcg9hach0d5acji5geh42gb
3.35kh9k813jk70fjjjl150i0ikg7mffmm4efak0ih8g3i0km4
3.39d911bclk3nn443
3.3dc9fine6e76llndlfjmi7k9n2fcnlh7m95927g497l0oaka
3.3higbebohjh2bh66ka19afih5lahe37b9h5ipiend7np4mjd
3.3m5q3m2dcpq63bohkl3n4gedlg4jjk1ii8g7qi8ngbeablk4
3.3r06liojplq9mr9eq1867957
3.4328n0cjqmic2nrmogpp06ff7hd864qe4kjg48db7da6hkcf
3.47d01ee07qs3an4tkttin4l91k8a7jrh1ot5gjqa431c5imf
3.4c25oe856s2t8tg5rue7psq0h3m72hd7tloa5ja67lj96li8
3.4gvml245kc
3.4m6dn4ow9qwe210nr3u0cdqkcnrbmwlh7kmfeapn9fijt38k
3.4rn5c8ianuxpep3owhg3n4m1o6r595s5kmr3djex4m1k6cph
3.4xfrgmtm53gd8tfed3xnstgi56yfaa7dvfrxe5vb5wq7qe4e
3.53i5ab8p5fc5vayqter60f6r

I have noticed that for binary I can have up to 48 fractional bits, 24 bits for quaternary, 16 bits for octal, 12 bits for hexadecimal and 9.6 bits for base-32, all other bases have precision limits under 48 bits, but interestingly ternary seems to have infinite precision...

So how is the code? Are there any areas that can be improved?

\$\begingroup\$ FYI, there's en.wikipedia.org/wiki/Base64, which shifts 0-9 to after the letters. \$\endgroup\$ – Teepeemm Apr 11, 2022 at 0:24

ALPHABET should be formed from ascii_letters and digits.

base having a default of 16 is kind of an odd choice? I think it would be less surprising to have no default at all and force the programmer to be explicit.

You call your validations useless but they seem fine to me (mostly). The one I would exclude is indicator. Rather than doing all of that hard work up front, it's just as good to attempt an index(d), catch an IndexError and raise ValueError(f'Digit {d} is invalid for base {base}').

Rather than hard-coding the number 36, it's best to assign that constant as len(ALPHABET) somewhere in the globals.

convertfrom_decimal (which should probably be called convert_from_decimal) has special treatment for the fractional component and integral component, which I think it shouldn't. You should just be able to get the mantissa, and then iteratively multiply and subtract until you hit a maximum number of digits or the remaining mantissa is zero.

Your use of if not re.match is a missed opportunity: the regex is not only useful as validation, but if you name and capture your groups, it can be used to parse the string as well.

index(d) is not as efficient as it could be. Rather than using the ALPHABET string opaquely, you could test to see whether the character is a letter or number and subtract accordingly, which will obviate any iteration through your ALPHABET string.

Thanks for contributing an answer to Code Review Stack Exchange!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.

To learn more, see our tips on writing great answers.