Closed Gabriel-p closed 7 years ago
And easier way to go about this:
from decimal import Decimal
import numpy as np
def format_5(f):
"""
Round floats that end with a '5' correctly, bypassing Python's issue
with the round() function.
See: http://codereview.stackexchange.com/q/122243/35351
"""
# If the last digit is 5 or larger, round up.
if int(f[-1]) >= 5:
r_up_down = 'ROUND_UP'
else:
r_up_down = 'ROUND_DOWN'
return Decimal.quantize(Decimal(f), Decimal(f[:-1]), rounding=r_up_down)
def count_zeros(sf):
"""
Count the number of leading zeros until the first non-zero digit.
Only works on float < 1.
"""
nd = 0
for d in sf[2:]: # Remove trailing '0.'
if d == '0':
nd += 1
else:
# Stop at the first non-zero digit found.
break
return nd
def round1(f):
"""
Round *positive* float to 1 significant figure.
"""
# Float as string.
sf = str(f)
if f == 0.:
f_r = 0.
elif 0. < f < 1.:
# Number of leading zeros.
nd = count_zeros(sf)
# If the float already has a single non-zero digit.
if len(sf) == (2 + nd + 1):
f_r = f
else:
# Isolate first two non-zero digits.
dn = sf[2+nd:nd+4]
# Create new float using the first two digits to round
# to a single digit.
sf_r = '0.' + dn
# Round digits.
sf_r5 = format_5(sf_r)
# Generate final properly rounded float.
# Catch special case.
if sf_r5 == Decimal('1.0'):
if nd > 0:
f_r = '0.' + '0'*nd + '1'
else:
f_r = 1.
else:
# General case.
f_r = '0.' + '0'*nd + str(sf_r5)[2:]
elif 1. <= f < 10.:
# Create float with first two digits, without the floating point.
sf_r = '0.' + sf[0] + sf[2]
# Round to one digit.
sf_r5 = format_5(sf_r)
# Catch special case.
if sf_r5 == Decimal('1.0'):
f_r = 10.
else:
# General case.
f_r = str(sf_r5)[-1]
else:
# Number of digits before the decimal point.
nb = len(sf.split('.')[0])
# Create float with first two digits.
sf_r = '0.' + sf[:2]
# Round to one digit.
sf_r5 = format_5(sf_r)
# Catch special case.
if sf_r5 == Decimal('1.0'):
f_r = '1' + str(sf_r5)[-1] + '0'*(nb-1) + '.'
else:
# General case.
f_r = str(sf_r5)[-1] + '0'*(nb-1) + '.'
return float(f_r)
f_lst = [0.01, 0.1, 0.99, 0.099, 0.95, 0.094, 0.00012, 0.055, 0.0075, 0.23]
f_lst = [1.0025, 5.57, 6.99, 9.49, 9.55, 9.99]
f_lst = [10., 10.05, 500.5, 556, 9956.2]
f_lst = np.random.uniform(10., 100000., 100)
for f in f_lst:
print 'orig:', f
f_r = round1(f)
print 'round:', f_r, '\n'
This issue raises unnecessary complications.
It's easier to simply output values with a fixed number of decimals, and let the user decide how to round them.
Sometimes parameter values like for example an age of
7.85
will be rounded to7.8
instead of7.9
.This is a known issue with Python and floats. I
sort of fixed it here.<-- did not.Related questions in SO:
For some clusters I've seen a value shown in screen, another printed in the output image, and another one to the output file. E.g. NGC294 (20th run): 0.000481 (screen), 0.0005 (image), 0.001 (output file). This is an issue with the rounding done for both the parameter value and its error.
The general problem is much more complicated and requires a lot of heuristics to get all of the cases right. The
errors_round
function needs a re-write to correctly handle them. Here's a start <-- Check new code below instead.And easier way to go about this:
Also, check uncertainties package.