On Thu, Feb 16, 2012 at 1:36 AM, Daniel Fetchinson <fetchin...@googlemail.com> wrote: >>> Hi folks, often times in science one expresses a value (say >>> 1.03789291) and its error (say 0.00089) in a short way by parentheses >>> like so: 1.0379(9) >>> >>> One can vary things a bit, but let's take the simplest case when we >>> only keep 1 digit of the error (and round it of course) and round the >>> value correspondingly. I've been searching around for a simple >>> function that would take 2 float arguments and would return a string >>> but didn't find anything although something tells me it's been done a >>> gazillion times. >>> >>> What would be the simplest such function? >> >> Well, this basically works: >> >>>>> def format_error(value, error): >> ... precision = int(math.floor(math.log(error, 10))) >> ... format = "%%.%df(%%d)" % max(-precision, 0) >> ... return format % (round(value, -precision), >> ... int(round(error / 10 ** precision))) >> ... >>>>> format_error(1.03789291, 0.00089) >> '1.0379(9)' >> >> Note that "math.floor(math.log(error, 10))" may return the wrong >> decimal precision due to binary floating point rounding error, which >> could produce some strange results: >> >>>>> format_error(10378929, 1000) >> '10378900(10)' >> >> So you'll probably want to use decimals instead: >> >> def format_error(value, error): >> value = decimal.Decimal(value) >> error = decimal.Decimal(error) >> value_scale = value.log10().to_integral(decimal.ROUND_FLOOR) >> error_scale = error.log10().to_integral(decimal.ROUND_FLOOR) >> precision = value_scale - error_scale >> if error_scale > 0: >> format = "%%.%dE" % max(precision, 0) >> else: >> format = "%%.%dG" % (max(precision, 0) + 1) >> value_str = format % value.quantize(decimal.Decimal("10") ** >> error_scale) >> error_str = '(%d)' % error.scaleb(-error_scale).to_integral() >> if 'E' in value_str: >> index = value_str.index('E') >> return value_str[:index] + error_str + value_str[index:] >> else: >> return value_str + error_str >> >>>>> format_error(1.03789291, 0.00089) >> '1.0379(9)' >>>>> format_error(103789291, 1000) >> '1.03789(1)E+08' >> >> I haven't tested this thoroughly, so use at your own risk. :-) > > Thanks a lot, this indeed mostly works, except for cases when the > error needs to be rounded up and becomes two digits: > >>>> format_error( '1.34883', '0.0098' ) > '1.349(10)' > > But in this case I'd like to see 1.35(1)
A small adjustment to the scale fixes that. Also tidied up the string formatting part: import decimal def format_error(value, error): value = decimal.Decimal(value) error = decimal.Decimal(error) error_scale = error.adjusted() error_scale += error.scaleb(-error_scale).to_integral().adjusted() value_str = str(value.quantize(decimal.Decimal("1E%d" % error_scale))) error_str = '(%d)' % error.scaleb(-error_scale).to_integral() if 'E' in value_str: index = value_str.index('E') return value_str[:index] + error_str + value_str[index:] else: return value_str + error_str Cheers, Ian -- http://mail.python.org/mailman/listinfo/python-list