format a measurement result and its error in "scientific" way
Ian Kelly
ian.g.kelly at gmail.com
Thu Feb 16 12:34:18 EST 2012
On Thu, Feb 16, 2012 at 1:36 AM, Daniel Fetchinson
<fetchinson at 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
More information about the Python-list
mailing list