Compare the number of significant digits in two numbers - python

For a coding exercise I'm working on, I'm trying to compare two numbers and choose the one that has the larger number of significant digits.
For example: compare 2.37e+07 and 2.38279e+07, select 2.38279e+07 because it has more significant digits.
I don't know how to implement this in Python. I considered counting the length of each number using len(str(NUMBER)), but this method returns "10" for both of the numbers above because it doesn't differentiate between zero and non-zero digits.
How can I compare the number of significant digits in Python?

A quick and dirty approach might be len(str(NUMBER).strip('0')) which will trim off any trailing zeros and count the remaining digits.
To discount the decimal point then you'd need len(str(NUMBER).replace('.','').strip('0'))
However you need to bear in mind that in many cases converting a python float to a string can give you some odd behaviour, due to the way floating point numbers are handled.

If you're going to go with doing this textually, you can do the following using regular expression:
import re
l = re.compile(r'(\d*?)(0*)(\.0?)')
>>> l.match(str(2.37e+07)).groups()[0]
'237'

Could try an algorithm like this:
sf1 = "2.3723805"
addsf1 = 0
decimal = False
for num in sf1:
if decimal == True:
addsf1 = addsf1 + int(num)
if num == ".":
decimal = True
print(addsf1)
This would check for every letter in the significant number, if you convert it to a string, that is. It will then iterate over every letter until it reaches the decimal part and then it will add the numbers together. You can do this to the other significant figure which can be used to compare the difference between the two. This would tell which one had the larger added numbers.
Not sure if this is the best solution for your problem.

Related

Taking just two decimals without rounding it

Basically, I have a list of float numbers with too many decimals. So when I created a second list with two decimals, Python rounded them. I used the following:
g1= ["%.2f" % i for i in g]
Where g1 is the new list with two decimals, but rounded, and g is the list with float numbers.
How can I make one without rounding them?
I'm a newbie, btw. Thanks!
So, you want to truncate the numbers at the second digit?
Beware that rounding might be the better and more accurate solution anyway.
If you want to truncate the numbers, there are a couple of ways - one of them is to multiply the number by 10 elevated to the number of desired decimal places (100 for 2 places), apply "math.floor", and divide the total back by the same number.
However, as internal floating point arithmetic is not base 10, you'd risk getting more decimal places on the division to scale down.
Another way is to create a string with 3 digits after the "." and drop the last one - that'd be rounding proof.
And again, keep in mind that this converts the numbers to strings - what should be done for presentation purposes only. Also, "%" formatting is quite an old way to format parameters in a string. In modern Python, f-strings are the preferred way:
g1 = [f"{number:.03f}"[:-1] for number in g]
Another, more correct way, is, of course, treat numbers as numbers, and not play tricks on adding or removing digits on it. As noted in the comments, the method above would work for numbers like "1.227", that would be kept as "1.22", but not for "2.99999", which would be rounded to "3.000" and then truncated to "3.00".
Python has the decimal modules, which allows for arbitrary precision of decimal numbers - which includes less precision, if needed, and control of the way Python does the rounding - including rounding towards zero, instead of the nearest number.
Just set the decimal context to the decimal.ROUND_DOWN strategy, and then convert your numbers using either the round built-in (the exact number of digits is guaranteed, unlike using round with floating point numbers), or just do the rounding as part of the string formatting anyway. You can also convert your floats do Decimals in the same step:
from decimals import Decimal as D, getcontext, ROUND_DOWN
getcontext().rounding = ROUND_DOWN
g1 = [f"{D(number):.02f}" for number in g]
Again - by doing this, you could as well keep your numbers as Decimal objects, and still be able to perform math operations on them:
g2 = [round(D(number, 2)) for number in g]
Here is my solution where we don't even need to convert the number's to string to get the desired output:
def format_till_2_decimal(num):
return int(num*100)/100.0
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
formatted_g = [format_till_2_decimal(num) for num in g]
print(formatted_g)
Hope this solution helps!!
Here might be the answer you are looking for:
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
def trunc(number, ndigits=2):
parts = str(number).split('.') # divides number into 2 parts. for ex: -5, and 4427926
truncated_number = '.'.join([parts[0], parts[1][:ndigits]]) # We keep this first part, while taking only 2 digits from the second part. Then we concat it together to get '-5.44'
return round(float(truncated_number), 2) # This should return a float number, but to make sure it is roundded to 2 decimals.
g1 = [trunc(i) for i in g]
print(g1)
[-5.42, -12.22, 7.21, -16.77, -6.14, 10.13, 14.74, 5.92, -9.74, -10.09]
Hope this helps.
Actually if David's answer is what you are looking for, it can be done simply as following:
g = [-5.427926, -12.222018, 7.214379, -16.771845, -6.1441464, 10.1383295, 14.740516, 5.9209185, -9.740783, -10.098338]
g1 = [("%.3f" % i)[:-1] for i in g]
Just take 3 decimals, and remove the last chars from the result strings. (You may convert the result to float if you like)

Print floating point number with a set number of significant digits (non-scientific)

I want to print floating point numbers with a set number of significant (i.e. non-zero) digits, but avoid the scientific notation (e).
So, for example, the number 0.000000002343245345 should be printed as 0.000000002343 (and not 2.343e-09)
I know how to define number of significant digits with an e notation:
>>>print('{:.3e}'.format(0.000000002343245345))
2.343e-09
And how to print a set number of decimal places without e-notation:
>>>print('{:.12f}'.format(0.000000002343245345))
0.000000002343
but not how to combine the two.
is there any simple way of doing so?
Here is some code that usually does what you want.
x = 0.000000002343245345
n = 4
from math import log10, floor
print('{:.{}f}'.format(x, n - floor(log10(x)) - 1))
Note that, due to the lack of exactness in floating-point arithmetic, that this may occasionally give a wrong answer. For example, the floor(log10()) may be one off from what is expected at or very near negative powers of 10 such as 0.1, 0.01, 0.001, and so on. My code seems to work well with those values but that is not guaranteed.
Also, there is no reasonable answer for some combinations of x and n. For example, what do you expect to result from x = 200000 and n = 4? There is no good answer, and my code gives the error
ValueError: Format specifier missing precision
You have to calculate the number of digits yourself. For four significant digits, this would be
number = 0.000000002343245345
digits = 4 - int(math.ceil(math.log10(number)))
print("{:.{}f}".format(number, digits))
# 0.000000002343

Why do I have to change integers to strings in order to iterate them in Python?

First of all, I have only recently started to learn Python on codeacademy.com and this is probably a very basic question, so thank you for the help and please forgive my lack of knowledge.
The function below takes positive integers as input and returns the sum of all that numbers' digits. What I don't understand, is why I have to change the type of the input into str first, and then back into integer, in order to add the numbers' digits to each other. Could someone help me out with an explanation please? The code works fine for the exercise, but I feel I am missing the big picture here.
def digit_sum(n):
num = 0
for i in str(n):
num += int(i)
return num
Integers are not sequences of digits. They are just (whole) numbers, so they can't be iterated over.
By turning the integer into a string, you created a sequence of digits (characters), and a string can be iterated over. It is no longer a number, it is now text.
See it as a representation; you could also have turned the same number into hexadecimal text, or octal text, or binary text. It would still be the same numerical value, just written down differently in text.
Iteration over a string works, and gives you single characters, which for a number means that each character is also a digit. The code takes that character and turns it back into a number with int(i).
You don't have to use that trick. You could also use maths:
def digit_sum(n):
total = 0
while n:
n, digit = divmod(n, 10)
num += digit
return num
This uses a while loop, and repeatedly divides the input number by ten (keeping the remainder) until 0 is reached. The remainders are summed, giving you the digit sum. So 1234 is turned into 123 and 4, then 12 and 3, etc.
Let's say the number 12345
So I would need 1,2,3,4,5 from the given number and then sum it up.
So how to get individuals number. One mathematical way was how #Martijn Pieters showed.
Another is to convert it into a string , and make it iterable.
This is one of the many ways to do it.
>>> sum(map(int, list(str(12345))))
15
The list() function break a string into individual letters. SO I needed a string. Once I have all numbers as individual letters, I can convert them into integers and add them up .

Thinking logically: Calculate how many times a certain number appears in an integer

Going through a self-learn book they gave you some code to find how many times a specific digit is in an integer or not. How did they automatically know to use modulo 10? Is this something you as a programmer learn a trick for in your CompSci classes?
def num_zero_and_five_digits(n):
count = 0
while n:
digit = n % 10 # This divides w/10 for remainder. How did they know to use 10?
if digit == 0 or digit == 5: #These can be changed to whatever digits you want.
count = count + 1
n = n / 10
return count
I understand the code, but don't own it. What I mean is that if I was asked to 'write code' that would find how many times a certain digit is in an integer, I would personally
do something like this:
integer = str(22342445)
looker = list(integer)
counter = 0
find = raw_input("What number are you looking for")
for num in looker:
if find == num:
print "We found it!"
counter += 1
print "There are %d, %s's in %s" % (counter, find,integer )
Now, my main questions are:
What if someone wants to look for the integer "10" or higher? How
can I account for that in the first solution?
What steps would you personally take to come up with a solution like the first? How would you just "know" that you needed to do modulo 10?
The exercise itself is improperly defined. Rather than looking for a specific number it should instead ask about looking for a specific numeral. This restricts it to, since we use decimal (base-10) numbers (by which I mean that we use the base-10 representation of numbers), one of 10 possibilities. And since we use base-10 numbers, we need to divide by and take the modulus of 10 to separate the number into its digits so that we can compare the numerals. If we were talking about a hexadecimal number instead then we would use 16 to separate the digits, for octal we would use 8, etc.

Python: indexing floats?

I have two sets of data that I am reading via nested for loops in Python. I need to match lines of the two different text files using a common number (time). In the two files, time is written differently (ex. 21:53:28.339 vs. 121082008.3399). I only need the last four digits of the times to match them up, for example from 21:53:28.339, I only need '8.339'. For the most part, indexing the number as a string works just fine (ex: timeList[nid][7:]), except for situations such as the numbers listed above, where python rounds .3399 to .34.
Is there a way for me to keep the numbers in float form and to select unrounded digits from the data?
Thanks!
edit - using Decimal exclusively - with full example
import decimal
def simplify(text):
# might be a : separated value
text = text.split(':')[-1]
# turn into decimal
number = decimal.Decimal(text)
# remove everything but the ones place and forwards
number = number - (number/10).quantize(1, rounding=decimal.ROUND_FLOOR) * 10
# truncate to the thousandths
return number.quantize(decimal.Decimal('.001'), rounding=decimal.ROUND_FLOOR)
a = '121082008.3399'
b = '21:53:28.339'
assert simplify(a) == simplify(b)
print simplify(a), '=', simplify(b)
Scott if you compare the numbers using strings then you don't need any floats and there will be no 'rounding' going on.
'8.339' == '8.339'
or, if you have
a = '8.3399'
b = '8.339'
then
a[:-1] == b
however if you do decide to work with them as 'numbers', then as Ignacio pointed out, you can use decimals.
from decimal import Decimal
number_a = Decimal(a[:-1])
number_b = Decimal(b)
now
number_a == number_b
Hope that helps
It appears from your description that you want to compare using one digit before the decimal point and 3 digits after the decimal point, using truncation instead of rounding. So just do that:
>>> def extract(s):
... i = s.find('.')
... return s[i-1:i+4]
...
>>> map(extract, ['21:53:28.339', '121082008.3399'])
['8.339', '8.339']
>>>
Use decimal.Decimal instead of float.

Categories