简体   繁体   中英

Python internal metric unit conversion function

I'm trying to build a function to do internal metric conversion on a wavelength to frequency conversion program and have been having a hard time getting it to behave properly. It is super slow and will not assign the correct labels to the output. If anyone can help with either a different method of computing this or a reason on why this is happening and any fixes that I cond do that would be amazing!

def convert_SI_l(n):
if n in range( int(1e-12),int(9e-11)):
    return n/0.000000000001, 'pm'
else:
    if n in range(int(1e-10),int(9e-8)):
         return n/0.000000001 , 'nm'
    else:
        if n in range(int(1e-7),int(9e-5)):
            return n/0.000001, 'um'
        else:
            if n in range(int(1e-4),int(9e-3)):
                return n/0.001, 'mm'
            else:
                if n in range(int(0.01), int(0.99)):
                    return n/0.01, 'cm'
                else:
                    if n in range(1,999):
                        return n/1000, 'm'
                    else:
                        if n in range(1000,299792459):
                            return n/1000, 'km'
                        else:
                            return n , 'm'

def convert_SI_f(n):
    if n in range( 1,999):
        return n, 'Hz'
    else:
        if n in range(1000,999999):
             return n/1000 , 'kHz'
        else:
            if n in range(int(1e6),999999999):
                return n/1e6, 'MHz'
            else:
                if n in range(int(1e9),int(1e13)):
                    return n/1e9, 'GHz'
                else:
                    return n, 'Hz'

c=299792458

i=input("Are we starting with a frequency or a wavelength? ( F / L ):  ")

#Error statements
if i.lower() == ("f"):
    True
else:
    if not i.lower() == ("l"):
        print ("Error invalid input")

#Cases 
if i.lower() == ("f"):
    f = float(input("Please input frequency (in Hz):  "))
    size_l = c/f
    print(convert_SI_l(size_l))

if i.lower() == ("l"):
    l = float(input("Please input wavelength (in meters):  "))
    size_f = ( l/c)
    print(convert_SI_f(size_f))

EDIT: Actually, range is a function which is generally used in itaration to generate numbers. So, when you write if n in range(min_value, max_value) , this function generates all integers until it finds a match or reach the max_value .

The range type represents an immutable sequence of numbers and is commonly used for looping a specific number of times in for loops.

Instead of writing:

if n in range(int(1e-10),int(9e-8)):
    return n/0.000000001 , 'nm'

you should write:

if 1e-10 <= n < 9e-8:
    return n/0.000000001 , 'nm'

Also keep in mind that range only works on integers, not float.

More EDIT:

For your specific use case, you can define dictionary of *(value, symbol) pairs, like below:

import collections

symbols = collections.OrderedDict(
    [(1e-12, u'p'),
     (1e-9, u'n'),
     (1e-6, u'μ'),
     (1e-3, u'm'),
     (1e-2, u'c'),
     (1e-1, u'd'),
     (1e0, u''),
     (1e1, u'da'),
     (1e2, u'h'),
     (1e3, u'k'),
     (1e6, u'M'),
     (1e9, u'G'),
     (1e12, u'T')])

The use the bisect.bisect function to find the "insertion" point of your value in that ordered collection. This insertion point can be used to get the simplified value and the SI symbol to use.

For instance:

import bisect


def convert_to_si(value):
    if value < 0:
        value, symbol = convert_to_si(-value)
        return -value, symbol
    elif value > 0:
        orders = list(symbols.keys())
        order_index = bisect.bisect(orders, value / 10.0)
        order = orders[min(order_index, len(orders) - 1)]
        return value / order, symbols[order]
    else:
        return value, u""

Demonstration:

for value in [1e-12, 3.14e-11, 0, 2, 20, 3e+9]:
    print(*convert_to_si(value), sep="")

You get:

1.0p
0.0314n
0
2.0
2.0da
3.0G

You can adapt this function to your needs…

You are using range() in a way that is close to how it is used in natural language, to express a contiguous segment of the real number line, as in in the range 4.5 to 5.25 . But range() doesn't mean that in Python. It means a bunch of integers. So your floating-point values, even if they are in the range you specify, will not occur in the bunch of integers that the range() function generates.

Your first test is

if n in range( int(1e-12),int(9e-11)):

and I am guessing you wrote it like this because what you actually wanted was range(1e-12, 9e-11) but you got TypeError: 'float' object cannot be interpreted as an integer .

But if you do this at the interpreter prompt

>>> range(int(1e-12),int(9e-11))
range(0, 0)
>>> list(range(int(1e-12),int(9e-11)))
[]

you will see it means something quite different to what you obviously expect.

To test if a floating-point number falls in a given range do

if lower-bound <= mynumber <= upper-bound:

You don't need ranges and your logic will be more robust if you base it on fixed threshold points that delimit the unit magnitude. This would typically be a unit of one in the given scale.

Here's a generalized approach to all unit scale determination:

SI_Length = [ (1/1000000000000,"pm"),
              (1/1000000000,   "nm"),
              (1/1000000,      "um"),
              (1/1000,         "mm"),
              (1/100,          "cm"),
              (1,              "m"),
              (1000,           "km") ]

SI_Frequency = [ (1, "Hz"), (1000,"kHz"), (1000000,"MHz"), (1000000000,"GHz")]

def convert(n,units):
    useFactor,useName = units[0]
    for factor,name in units:
        if n >= factor : useFactor,useName = factor,name
    return (n/useFactor,useName)

print(convert(0.0035,SI_Length)) # 3.5 mm
print(convert(12332.55,SI_Frequency)) # 12.33255 kHz

Each unit array must be in order of smallest to largest multiplier.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM