Keep only two digits after the first digit other than zero
Question:
I know how to keep only two decimal places after the decimal point. For example (there are other methods):
>>> print(f'{10000.01908223295211791992:.2f}')
10000.02
But I want to keep the first two digits other than zero after the decimal point:
10000.01908223295211791992 will give: 10000.019
0.0000456576578765 will give: 0.000046
Is there an built-in method that I’m missing? Or is the only solution to code a test for (almost) every case?
Answers:
This code works as you want, I define a function because there are some exception in doing this rounding operation:
import decimal
# Set the precision of the decimal module to 20, it should be more than enough
ctx = decimal.Context()
ctx.prec = 20
# utility function to convert float to string
def float_to_str(f):
d1 = ctx.create_decimal(repr(f))
return format(d1, 'f')
def format_first_two_non_zero_digits(number):
number_str = float_to_str(number) # Convert the number to a string
decimal_point_index = number_str.find('.') # Find the position of the decimal point
# If there is no decimal point, return the original number
if decimal_point_index == -1:
return number_str
# Iterate through the digits after the decimal point
for i in range(decimal_point_index + 1, len(number_str)):
if number_str[i] != '0':
# Found the first non-zero digit after the decimal point
return number_str[:i + 2]
return number_str # If all digits after the decimal point are zero, return the original number
# Examples
number1 = 10000.01908223295211791992
number2 = 0.0000456576578765
formatted1 = format_first_two_non_zero_digits(number1)
formatted2 = format_first_two_non_zero_digits(number2)
print(formatted1)
print(formatted2)
Marco’s answer is certainly valid, but I think it reinvents the wheel a bit when this could be solved with regular expressions:
import re
def rounder(number):
pattern = re.compile(r'(d+.(([1-9]*)?|0+)0[1-9]{2})') #one or more numbers, a decimal point, then either any number of non-0s, or one or more zeros, followed by a zero and 2 other numbers
if result:=pattern.match(str(number)):
return(float(result.group(1)))
else:
return(None)
If the regex doesn’t match, it will simply return None
—you can tweak this if you need error-handling.
Thanks to all the comments, I think the rounder_2 function is a good candidate.
import math
def rounder_2(x):
"""Round a value to two significant figures.
If x is an integer or have only zeros after the decimal point, no need to round.
If x is a float, keep only the first two digits other than zero after the decimal point
"""
try:
return round(x, -int(math.floor(math.log10(abs(math.modf(x)[0]))))+1)
except ValueError:
return x
The following should be able to match the string and then round the value by applying a magnitude transformation.
import math
import re
TWO_DIGITS_AFTER_ZERO = re.compile(r'(d+.(?:(?:[1-9]*)?|0+)0[1-9]{2})')
def truncate(n: float) -> float:
if result := TWO_DIGITS_AFTER_ZERO.match(format(n, 'f')):
value = result.group(0)
magnitude = 10**len(value.split('.')[1])
return round(float(value) * magnitude) / magnitude
else:
return n
if __name__ == '__main__':
assert math.isclose(truncate(10000.01908223295211791992), 10000.019)
assert math.isclose(truncate(0.0000456576578765), 0.000046)
Here is an example utilizing the ndigits
keyword argument of the round()
function:
from math import floor, isclose, log10, modf
from typing import Callable
f: Callable[[float],float] = lambda n: round(n, -int(floor(log10(abs(modf(n)[0]))))+1)
if __name__ == '__main__':
assert isclose(f(10000.01908223295211791992), 10000.019)
assert isclose(f(0.0000456576578765), 0.000046)
I know how to keep only two decimal places after the decimal point. For example (there are other methods):
>>> print(f'{10000.01908223295211791992:.2f}')
10000.02
But I want to keep the first two digits other than zero after the decimal point:
10000.01908223295211791992 will give: 10000.019
0.0000456576578765 will give: 0.000046
Is there an built-in method that I’m missing? Or is the only solution to code a test for (almost) every case?
This code works as you want, I define a function because there are some exception in doing this rounding operation:
import decimal
# Set the precision of the decimal module to 20, it should be more than enough
ctx = decimal.Context()
ctx.prec = 20
# utility function to convert float to string
def float_to_str(f):
d1 = ctx.create_decimal(repr(f))
return format(d1, 'f')
def format_first_two_non_zero_digits(number):
number_str = float_to_str(number) # Convert the number to a string
decimal_point_index = number_str.find('.') # Find the position of the decimal point
# If there is no decimal point, return the original number
if decimal_point_index == -1:
return number_str
# Iterate through the digits after the decimal point
for i in range(decimal_point_index + 1, len(number_str)):
if number_str[i] != '0':
# Found the first non-zero digit after the decimal point
return number_str[:i + 2]
return number_str # If all digits after the decimal point are zero, return the original number
# Examples
number1 = 10000.01908223295211791992
number2 = 0.0000456576578765
formatted1 = format_first_two_non_zero_digits(number1)
formatted2 = format_first_two_non_zero_digits(number2)
print(formatted1)
print(formatted2)
Marco’s answer is certainly valid, but I think it reinvents the wheel a bit when this could be solved with regular expressions:
import re
def rounder(number):
pattern = re.compile(r'(d+.(([1-9]*)?|0+)0[1-9]{2})') #one or more numbers, a decimal point, then either any number of non-0s, or one or more zeros, followed by a zero and 2 other numbers
if result:=pattern.match(str(number)):
return(float(result.group(1)))
else:
return(None)
If the regex doesn’t match, it will simply return None
—you can tweak this if you need error-handling.
Thanks to all the comments, I think the rounder_2 function is a good candidate.
import math
def rounder_2(x):
"""Round a value to two significant figures.
If x is an integer or have only zeros after the decimal point, no need to round.
If x is a float, keep only the first two digits other than zero after the decimal point
"""
try:
return round(x, -int(math.floor(math.log10(abs(math.modf(x)[0]))))+1)
except ValueError:
return x
The following should be able to match the string and then round the value by applying a magnitude transformation.
import math
import re
TWO_DIGITS_AFTER_ZERO = re.compile(r'(d+.(?:(?:[1-9]*)?|0+)0[1-9]{2})')
def truncate(n: float) -> float:
if result := TWO_DIGITS_AFTER_ZERO.match(format(n, 'f')):
value = result.group(0)
magnitude = 10**len(value.split('.')[1])
return round(float(value) * magnitude) / magnitude
else:
return n
if __name__ == '__main__':
assert math.isclose(truncate(10000.01908223295211791992), 10000.019)
assert math.isclose(truncate(0.0000456576578765), 0.000046)
Here is an example utilizing the ndigits
keyword argument of the round()
function:
from math import floor, isclose, log10, modf
from typing import Callable
f: Callable[[float],float] = lambda n: round(n, -int(floor(log10(abs(modf(n)[0]))))+1)
if __name__ == '__main__':
assert isclose(f(10000.01908223295211791992), 10000.019)
assert isclose(f(0.0000456576578765), 0.000046)