Welcome, guest | Sign In | My Account | Store | Cart

Method to find significant digits for any given number. Accounts for scientific notation. Returns a numeric string.

A unit test is included.

Python, 104 lines

Significant digits are important in many scientific and engineering applications, and anything involving measurement and calculation.

For an introduction to significant digits: http://www.physics.uoguelph.ca/tutorials/sig_fig/SIG_dig.htm

Known Issues:

If all the significant digits are to the left of the decimal, then it always returns in scientific notation. That is, 3300 becomes 3.3e+3 and 33 becames 3.3e+1. This is a feature, not a bug. ;) Actually, I discovered this rule late in the process, and I really didn't feel like writing another rule to make 33.0 become 33. :P It is ugly, but technically correct.

I suppose some people might actually prefer that all cases returned scientific notation. However, I'm not one of those people.

The unit test could use more test cases. Bug reports and refactoring suggestions are welcome--the implementation is rather byzantine.

Disclaimer:

I wrote this mainly by memory from what I learned in high school physics. I might have missed something, because Mr. Rast was always docking me for getting my sigdigs wrong. :P

Fixed (or rather, caught) an UnboundLocalError that raised if the given number was zero. It now returns the string '0.0', which seems correct. Any dissenters?

5-Sep-2007: So, it's kind of embarrassing still having this recipe out here. I will clean it up one of these days. Until then, see the comments below for something actually useful.

Alexander Semenov 18 years, 11 months ago

Your code is monstrous, if I will need this I'll write that.

def myMakeSigDigs(num, digits, debug=False):
digs, order = ('%.20e'%num).split('e')
order = int(order)
if type(num) is long: digs = str(num) # Not needed for current tests
digs = (digs.replace('.', '') + '0'*digits)[:digits]

if debug: print 'num=%r, order=%d, digits=%s'%(num, order, digs)

if 0&lt;=order&lt;5 and order&lt;len(digs)-1:
return '%s.%s'%(digs[:order+1], digs[order+1:])
elif -5&lt;=order&lt;0:
return '0.%s%s'%('0'*(-order-1), digs)
else:
return '%s.%se%+d'%(digs[0], digs[1:], order)

This code passes your unit test. If you work only with floats, and don't want add to tests

[1234567890123456789012L, 19, '1.234567890123456789e+21']

code may be even one line shorter (see comments).

Alexander Semenov 18 years, 11 months ago

Just for fun. when not using longs calculating order and digits can be written with following one-liner: digs, order = [[lambda x, d=digits: (x.replace('.', '') + '0'*d)[:d], int][func](arg) for func, arg in enumerate(('%.20e'%num).split('e'))] I didn't saw such trick before. It is a map() which applies different functions to record fields.

Alexander Semenov 18 years, 11 months ago

minor bug in your code. When I ran this recipe i got:

======================================================================
FAIL: MakeSigDigs should return known values for known inputs.
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Usr\Sav\wrk\prj\digs\digicalc.py", line 118, in testKnownValues
self.assertEqual(makeSigDigs(el[0], el[1], debug=True), el[2])
AssertionError: '3.3e-010' != '3.3e-10'

----------------------------------------------------------------------
David Eyk (author) 18 years, 11 months ago

Thanks for the tips. When I have a moment, I'll see if I can refactor my code based on your ideas. I'm fairly new with python, hence the monstrosity. ;) Thanks.

Brian Boonstra 17 years, 7 months ago

Neither of the above recipes actually works. When asked for two significant digits of 0.9999, both of the above recipes return the wrong result of 0.99, rather than the correct result of 1.0.

Brian Boonstra 17 years, 5 months ago

Better for some. Try Tim Peters' solution http://mail.python.org/pipermail/tutor/2004-July/030324.html

John Hill 15 years, 11 months ago

Love the test case, what about (0.0009, 2)? Alexander,

I decided to try your improved version before trying the original version (which looks more like something I wrote).

However: myMakeSigDigs(0.0009, 2) -> 0.00089, which I submit will not do. I would have expected to see 0.00090 the reason this matters to me is that I work with reports from chemical analysis, and folks will sometimes switch from mg/L to ug/L or vice versa, in the latter case they would expect 0.00090 to become 0.90 and 0.89 is not a good match.

 Created by David Eyk on Thu, 17 Mar 2005 (PSF)