To directly answer the question, here's my version using naming from the R function:
import math
def signif(x, digits=6):
if x == 0 or not math.isfinite(x):
return x
digits -= math.ceil(math.log10(abs(x)))
return round(x, digits)
My main reason for posting this answer are the comments complaining that "0.075" rounds to 0.07 rather than 0.08. This is due, as pointed out by "Novice C", to a combination of floating point arithmetic having both finite precision and a base-2 representation. The nearest number to 0.075 that can actually be represented is slightly smaller, hence rounding comes out differently than you might naively expect.
Also note that this applies to any use of non-decimal floating point arithmetic, e.g. C and Java both have the same issue.
To show in more detail, we ask Python to format the number in "hex" format:
0.075.hex()
which gives us: 0x1.3333333333333p-4
. The reason for doing this is that the normal decimal representation often involves rounding and hence is not how the computer actually "sees" the number. If you're not used to this format, a couple of useful references are the Python docs and the C standard.
To show how these numbers work a bit, we can get back to our starting point by doing:
0x13333333333333 / 16**13 * 2**-4
which should should print out 0.075
. 16**13
is because there are 13 hexadecimal digits after the decimal point, and 2**-4
is because hex exponents are base-2.
Now we have some idea of how floats are represented we can use the decimal
module to give us some more precision, showing us what's going on:
from decimal import Decimal
Decimal(0x13333333333333) / 16**13 / 2**4
giving: 0.07499999999999999722444243844
and hopefully explaining why round(0.075, 2)
evaluates to 0.07
1000.0
with trailing decimal points and/or zeros, which in standard practice indicate far more precision. – nealmcb