Append a plus sign before positive numbers - python

I want to append a plus sign before numbers. I am already using format specifier:
"{0:+03f}".format(x)
And I've heard about those two as well, but I don't know how to use them:
"%+d" or "%+f"
My problem with the first one is the fact that the number after format is in float type.
For example, I am making a small program to calculate quadratic function and I am not satisfied with output like this:
f(x) = 2x^2+2.000x-4.000000
Those zeros are making it looks weird.
If not above, is there any solution to get rid of zeros when there is nothing, but only zeros after the dot?

Perhaps %g is what you're looking for?
>>> '%+g' % 2.
'+2'
>>> '%+g' % 2.1
'+2.1'
>>> '%+g' % 2.10001
'+2.10001'
The exact definition of %g is as follows:
General format. For a given precision p >= 1, this rounds the number
to p significant digits and then formats the result in either
fixed-point format or in scientific notation, depending on its
magnitude.
The precise rules are as follows: suppose that the result formatted
with presentation type 'e' and precision p-1 would have exponent exp.
Then if -4 <= exp < p, the number is formatted with presentation type
'f' and precision p-1-exp. Otherwise, the number is formatted with
presentation type 'e' and precision p-1. In both cases insignificant
trailing zeros are removed from the significand, and the decimal point
is also removed if there are no remaining digits following it.
Positive and negative infinity, positive and negative zero, and nans,
are formatted as inf, -inf, 0, -0 and nan respectively, regardless of
the precision.
A precision of 0 is treated as equivalent to a precision of 1. The
default precision is 6.
(source.)
Similarly with format():
>>> '{0:+g}'.format(2.)
'+2'
>>> '{0:+g}'.format(2.1)
'+2.1'
>>> '{0:+g}'.format(2.1001)
'+2.1001'

%+g works but uses (very) old-style formatting. Python has evolved to provide 2 modern formatting methods:
New style formatting:
>>> "{0:+g}".format(2.00001)
'+2.00001'
>>> "{0:+g}".format(-2.00001)
'-2.00001'
>>> "{0:+g}".format(2)
'+2'
and with python 3.6+ f-strings:
>>> value = 2
>>> f"{value:+g}"
'+2'
>>> value = -2.00001
>>> f"{value:+g}"
'-2.00001'
>>> value = 2.00001
>>> f"{value:+g}"
'+2.00001'

Related

Displaying floats using F-string

I'm really curious about why the behaviour is so different when add this "f" to the end of the number which I want to display:
# CASE with f
float1 = 10.1444786
print(f"{float1:.4f}")
# print out: 10.1445
# CASE without f
print(f"{float1:.4}")
# print out: 10.14
Why only 2 characters are displayed in the second example?
The implied type specifier is g, as given in the documentation Thanks #Barmar for adding a comment with this info!
None: For float this is the same as 'g', except that when fixed-point notation is used to format the result, it always includes at least one digit past the decimal point. The precision used is as large as needed to represent the given value faithfully.
For Decimal, this is the same as either 'g' or 'G' depending on the value of context.capitals for the current decimal context.
The overall effect is to match the output of str() as altered by the other format modifiers.
An experiment:
for _ in range(10000):
r = random.random() * random.randint(1, 10)
assert f"{r:.6}" == f"{r:.6g}"
Works every time
From https://docs.python.org/3/library/string.html#formatstrings,
General format. For a given precision p >= 1, this rounds the number to p significant digits and then formats the result in either fixed-point format or in scientific notation, depending on its magnitude. A precision of 0 is treated as equivalent to a precision of 1.
So in your second example, you ask for 4 sigfigs, but in your first you ask for 4 digits of precision.

Round 2 digit after decimal point after zero in Python

Assume I have a float:
x = 0.0005953829144211724
I have to round it after the decimal to:
x = 0.00059
Similarly, if
x = 0.00000046605219739046376
then the result should be
x = 0.00000046
Is there any inbuild function in python to do this?
You can use a nested format with Decimal. The first format does the rounding using the "g" specifier. The second one prints all the digits, without scientific notation, using the decimal value of the rounded string:
from decimal import Decimal
x = 0.0005953829144211724
print(f"{Decimal(f'{x:.2g}'):f}") # 0.0006
print(f"{Decimal(f'{x:.3g}'):f}") # 0.000595
print(f"{Decimal(f'{x:.4g}'):f}") # 0.0005954
x = 0.00000046605219739046376
print(f"{Decimal(f'{x:.2g}'):f}") # 0.00000047
print(f"{Decimal(f'{x:.3g}'):f}") # 0.000000466
print(f"{Decimal(f'{x:.4g}'):f}") # 0.0000004661
Note that this DOES round the value to the specified precision, contrary to your examples which truncate the mantissa instead of rounding it
As shown in the other answer, if you want a certain number of significant digits, you should format the number in scientific notation. If, however, you want those significant digits in the "normal" format, you might either convert that scientific notation back to float (thus "forgetting" all the "insignificant" digits) and then back to string and rstrip all excess zeros, or maybe use a regular expression:
>>> x = 0.00000046605219739046376
>>> f'{float(f"{x:.2g}"):.20f}'.rstrip("0")
'0.00000047'
>>> re.match(r"0\.0*[^0]{2}", f"{x:.20f}").group()
'0.00000046'
Note: i) The .20f here means "print in normal decimal format with 20 places after the decimal", where the 20 is kind of arbitrary. ii) The regex will not round but just trim the number.
You can do something close with the g specifier in an f-string:
x = 0.00000046605219739046376
print(f'{x:.2g}')
This will print the result in "scientific notation"
4.7e-07
Similarly:
x = 0.0005953829144211724
print(f'{x:.2g}')
results in
0.0006
since it rounds up.

Format float with fixed amount of digits python

I would like to format my floats with a fixed amount of digits. Right now I'm doing the following
format="%6.6g"
print(format%0.00215165)
print(format%1.23260)
print(format%145.5655)
But this outputs
0.00215165
1.2326
145.565
I also tried format="%6.6f" but it doesn't really give what I want either...
0.002152
1.232600
145.565500
What would be a good way to format the numbers so that all of them have exactly width 6 (and no spaces) like so ?
0.002152
1.232600
145.5655
This is complicated because you want the precision (number of decimals) to depend on the available space, while the general thrust of floating-point formatting is to make the number of significant digits depend on the available space. To do what you want you need a function that computes the desired number of decimals from the log of the number. There isn't, so far as I know, a built-in function that will do this for you.
def decimals(v):
return max(0, min(6,6-int(math.log10(abs(v))))) if v else 6
This simply takes the log of number and truncates it to int. So 10-99 -> 1, 100-999 -> 2 etc. You then use that
result to work out the precision to which the number needs to be formatted. In practice the
function is more complex because of the corner cases: what to do with negative numbers, numbers that underflow, etc.
For simplicity I've deliberately left your figure of 6 decimals hard-coded 3 times in the function.
Then formatting isn't so hard:
>>> v = 0.00215165
>>> "{0:.{1}f}".format(v, decimals(v))
'0.002152'
>>> v2 = 1.23260
>>> "{0:.{1}f}".format(v2, decimals(v2))
'1.232600'
>>> v3 = 145.5655
>>> "{0:.{1}f}".format(v3, decimals(v3))
'145.5655'
>>> vz = 0e0 # behaviour with zero
>>> "{0:.{1}f}".format(vz, decimals(vz))
'0.000000'
>>> vu = 1e-10 # behaviour with underflow
>>> "{0:.{1}f}".format(vu, decimals(vu))
'0.000000'
>>> vo = 1234567 # behaviour when nearly out of space
>>> "{0:.{1}f}".format(vo, decimals(vo))
'1234567'
>>> voo = 12345678 # behaviour when all out of space
>>> "{0:.{1}f}".format(voo, decimals(voo))
'12345678'
You can use %-notation for this instead of a call to format but it is not very obvious or intuitive:
>>> "%.*f" % (decimals(v), v)
'0.002152'
You don't say what you want done with negative numbers. What this approach does is to take an extra
character to display the minus sign. If you don't want that then you need to reduce the number of
decimals for negative numbers.

How to find an original text representation for lower precision float values in Python?

I've run into an issue displaying float values in Python, loaded from an external data-source(they're 32bit floats, but this would apply to lower precision floats too).
(In case its important - These values were typed in by humans in C/C++, so unlike arbitrary calculated values, deviations from round numbers is likely not intended, though can't be ignored since the values may be constants such as M_PI or multiplied by constants).
Since CPython uses higher precision, (64bit typically), a value entered in as a lower precision float may repr() showing precision loss from being a 32bit-float, where the 64bit-float would show round values.
eg:
# Examples of 32bit float's displayed as 64bit floats in CPython.
0.0005 -> 0.0005000000237487257
0.025 -> 0.02500000037252903
0.04 -> 0.03999999910593033
0.05 -> 0.05000000074505806
0.3 -> 0.30000001192092896
0.98 -> 0.9800000190734863
1.2 -> 1.2000000476837158
4096.3 -> 4096.2998046875
Simply rounding the values to some arbitrary precision works in most cases, but may be incorrect since it could loose significant values with eg: 0.00000001.
An example of this can be shown by printing a float converted to a 32bit float.
def as_float_32(f):
from struct import pack, unpack
return unpack("f", pack("f", f))[0]
print(0.025) # --> 0.025
print(as_float_32(0.025)) # --> 0.02500000037252903
So my question is:
Whats the most efficient & straightforward way to get the original representation for a 32bit float, without making assumptions or loosing precision?
Put differently, if I have a data-source containing of 32bit floats, These were originally entered in by a human as round values, (examples above), but having them represented as higher precision values exposes that the value as a 32bit float is an approximation of the original value.
I would like to reverse this process, and get the round number back from the 32bit float data, but without loosing the precision which a 32bit float gives us. (which is why simply rounding isn't a good option).
Examples of why you might want to do this:
Generating API documentation where Python extracts values from a C-API that uses single precision floats internally.
When people need to read/review values of data generated which happens to be provided as single precision floats.
In both cases it's important not to loose significant precision, or show values which can't be easily read by humans at a glance.
Update, I've made a solution which I'll include as an answer (for reference and to show its possible), but highly doubt its an efficient or elegant solution.
Of course you can't know the notation used: 0.1f, 0.1F or 1e-1f where entered, that's not the purpose of this question.
You're looking to solve essentially the same problem that Python's repr solves, namely, finding the shortest decimal string that rounds to a given float. Except that in your case, the float isn't an IEEE 754 binary64 ("double precision") float, but an IEEE 754 binary32 ("single precision") float.
Just for the record, I should of course point out that retrieving the original string representation is impossible, since for example the strings '0.10', '0.1', '1e-1' and '10e-2' all get converted to the same float (or in this case float32). But under suitable conditions we can still hope to produce a string that has the same decimal value as the original string, and that's what I'll do below.
The approach you outline in your answer more-or-less works, but it can be streamlined a bit.
First, some bounds: when it comes to decimal representations of single-precision floats, there are two magic numbers: 6 and 9. The significance of 6 is that any (not-too-large, not-too-small) decimal numeric string with 6 or fewer significant decimal digits will round-trip correctly through a single-precision IEEE 754 float: that is, converting that string to the nearest float32, and then converting that value back to the nearest 6-digit decimal string, will produce a string with the same value as the original. For example:
>>> x = "634278e13"
>>> y = float(np.float32(x))
>>> y
6.342780214942106e+18
>>> "{:.6g}".format(y)
'6.34278e+18'
(Here, by "not-too-large, not-too-small" I just mean that the underflow and overflow ranges of float32 should be avoided. The property above applies for all normal values.)
This means that for your problem, if the original string had 6 or fewer digits, we can recover it by simply formatting the value to 6 significant digits. So if you only care about recovering strings that had 6 or fewer significant decimal digits in the first place, you can stop reading here: a simple '{:.6g}'.format(x) is enough. If you want to solve the problem more generally, read on.
For roundtripping in the other direction, we have the opposite property: given any single-precision float x, converting that float to a 9-digit decimal string (rounding to nearest, as always), and then converting that string back to a single-precision float, will always exactly recover the value of that float.
>>> x = np.float32(3.14159265358979)
>>> x
3.1415927
>>> np.float32('{:.9g}'.format(x)) == x
True
The relevance to your problem is there's always at least one 9-digit string that rounds to x, so we never have to look beyond 9 digits.
Now we can follow the same approach that you used in your answer: first try for a 6-digit string, then a 7-digit, then an 8-digit. If none of those work, the 9-digit string surely will, by the above. Here's some code.
def original_string(x):
for places in range(6, 10): # try 6, 7, 8, 9
s = '{:.{}g}'.format(x, places)
y = np.float32(s)
if x == y:
return s
# If x was genuinely a float32, we should never get here.
raise RuntimeError("We should never get here")
Example outputs:
>>> original_string(0.02500000037252903)
'0.025'
>>> original_string(0.03999999910593033)
'0.04'
>>> original_string(0.05000000074505806)
'0.05'
>>> original_string(0.30000001192092896)
'0.3'
>>> original_string(0.9800000190734863)
'0.98'
However, the above comes with several caveats.
First, for the key properties we're using to be true, we have to assume that np.float32 always does correct rounding. That may or may not be the case, depending on the operating system. (Even in cases where the relevant operating system calls claim to be correctly rounded, there may still be corner cases where that claim fails to be true.) In practice, it's likely that np.float32 is close enough to correctly rounded not to cause issues, but for complete confidence you'd want to know that it was correctly rounded.
Second, the above won't work for values in the subnormal range (so for float32, anything smaller than 2**-126). In the subnormal range, it's no longer true that a 6-digit decimal numeric string will roundtrip correctly through a single-precision float. If you care about subnormals, you'd need to do something more sophisticated there.
Third, there's a really subtle (and interesting!) error in the above that almost doesn't matter at all. The string formatting we're using always rounds x to the nearest places-digit decimal string to the true value of x. However, we want to know simply whether there's any places-digit decimal string that rounds back to x. We're implicitly assuming the (seemingly obvious) fact that if there's any places-digit decimal string that rounds to x, then the closest places-digit decimal string rounds to x. And that's almost true: it follows from the property that the interval of all real numbers that rounds to x is symmetric around x. But that symmetry property fails in one particular case, namely when x is a power of 2.
So when x is an exact power of 2, it's possible (but fairly unlikely) that (for example) the closest 8-digit decimal string to x doesn't round to x, but nevertheless there is an 8-digit decimal string that does round to x. You can do an exhaustive search for cases where this happens within the range of a float32, and it turns out that there are exactly three values of x for which this occurs, namely x = 2**-96, x = 2**87 and x = 2**90. For 7 digits, there are no such values. (And for 6 and 9 digits, this can never happen.) Let's take a closer look at the case x = 2**87:
>>> x = 2.0**87
>>> x
1.5474250491067253e+26
Let's take the closest 8-digit decimal value to x:
>>> s = '{:.8g}'.format(x)
>>> s
'1.547425e+26'
It turns out that this value doesn't round back to x:
>>> np.float32(s) == x
False
But the next 8-digit decimal string up from it does:
>>> np.float32('1.5474251e+26') == x
True
Similarly, here's the case x = 2**-96:
>>> x = 2**-96.
>>> x
1.262177448353619e-29
>>> s = '{:.8g}'.format(x)
>>> s
'1.2621774e-29'
>>> np.float32(s) == x
False
>>> np.float32('1.2621775e-29') == x
True
So ignoring subnormals and overflows, out of all 2 billion or so positive normal single-precision values, there are precisely three values x for which the above code doesn't work. (Note: I originally thought there was just one; thanks to #RickRegan for pointing out the error in comments.) So here's our (slightly tongue-in-cheek) fixed code:
def original_string(x):
"""
Given a single-precision positive normal value x,
return the shortest decimal numeric string which produces x.
"""
# Deal with the three awkward cases.
if x == 2**-96.:
return '1.2621775e-29'
elif x == 2**87:
return '1.5474251e+26'
elif x == 2**90:
return '1.2379401e+27'
for places in range(6, 10): # try 6, 7, 8, 9
s = '{:.{}g}'.format(x, places)
y = np.float32(s)
if x == y:
return s
# If x was genuinely a float32, we should never get here.
raise RuntimeError("We should never get here")
I think Decimal.quantize() (to round to a given number of decimal digits) and .normalize() (to strip trailing 0's) is what you need.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from decimal import Decimal
data = (
0.02500000037252903,
0.03999999910593033,
0.05000000074505806,
0.30000001192092896,
0.9800000190734863,
)
for f in data:
dec = Decimal(f).quantize(Decimal('1.0000000')).normalize()
print("Original %s -> %s" % (f, dec))
Result:
Original 0.0250000003725 -> 0.025
Original 0.0399999991059 -> 0.04
Original 0.0500000007451 -> 0.05
Original 0.300000011921 -> 0.3
Original 0.980000019073 -> 0.98
Heres a solution I've come up with which works (perfectly as far as I can tell) but isn't efficient.
It works by rounding at increasing decimal places, and returning the string when the rounded and non-rounded inputs match (when compared as values converted to lower precision).
Code:
def round_float_32(f):
from struct import pack, unpack
return unpack("f", pack("f", f))[0]
def as_float_low_precision_repr(f, round_fn):
f_round = round_fn(f)
f_str = repr(f)
f_str_frac = f_str.partition(".")[2]
if not f_str_frac:
return f_str
for i in range(1, len(f_str_frac)):
f_test = round(f, i)
f_test_round = round_fn(f_test)
if f_test_round == f_round:
return "%.*f" % (i, f_test)
return f_str
# ----
data = (
0.02500000037252903,
0.03999999910593033,
0.05000000074505806,
0.30000001192092896,
0.9800000190734863,
1.2000000476837158,
4096.2998046875,
)
for f in data:
f_as_float_32 = as_float_low_precision_repr(f, round_float_32)
print("%s -> %s" % (f, f_as_float_32))
Outputs:
0.02500000037252903 -> 0.025
0.03999999910593033 -> 0.04
0.05000000074505806 -> 0.05
0.30000001192092896 -> 0.3
0.9800000190734863 -> 0.98
1.2000000476837158 -> 1.2
4096.2998046875 -> 4096.3
If you have at least NumPy 1.14.0, you can just use repr(numpy.float32(your_value)). Quoting the release notes:
Float printing now uses “dragon4” algorithm for shortest decimal representation
The str and repr of floating-point values (16, 32, 64 and 128 bit) are now printed to give the shortest decimal representation which uniquely identifies the value from others of the same type. Previously this was only true for float64 values. The remaining float types will now often be shorter than in numpy 1.13.
Here's a demo running against a few of your example values:
>>> repr(numpy.float32(0.0005000000237487257))
'0.0005'
>>> repr(numpy.float32(0.02500000037252903))
'0.025'
>>> repr(numpy.float32(0.03999999910593033))
'0.04'
Probably what you are looking for is decimal:
Decimal “is based on a floating-point model which was designed with people in mind, and necessarily has a paramount guiding principle – computers must provide an arithmetic that works in the same way as the arithmetic that people learn at school.”
At least in python3 you can use .as_integer_ratio. That's not exactly a string but the floating point definition as such is not really well suited for giving an exact representation in "finite" strings.
a = 0.1
a.as_integer_ratio()
(3602879701896397, 36028797018963968)
So by saving these two numbers you'll never lose precision because these two exactly represent the saved floating point number. (Just divide the first by the second to get the value).
As an example using numpy dtypes (very similar to c dtypes):
# A value in python floating point precision
a = 0.1
# The value as ratio of integers
b = a.as_integer_ratio()
import numpy as np
# Force the result to have some precision:
res = np.array([0], dtype=np.float16)
np.true_divide(b[0], b[1], res)
print(res)
# Compare that two the wanted result when inputting 0.01
np.true_divide(1, 10, res)
print(res)
# Other precisions:
res = np.array([0], dtype=np.float32)
np.true_divide(b[0], b[1], res)
print(res)
res = np.array([0], dtype=np.float64)
np.true_divide(b[0], b[1], res)
print(res)
The result of all these calculations is:
[ 0.09997559] # Float16 with integer-ratio
[ 0.09997559] # Float16 reference
[ 0.1] # Float32
[ 0.1] # Float64

Convert number to string scientific notation fixed length

I have a normal float number such as "1234.567" or "100000". I would like to convert it to a string such that the precision is fixed and the number is in scientific notation. For example, with 5 digits, the results would be "1.2346e003 and "1.0000e005", respectively. The builtin Decimal number -> string functions will round it if it can, so the second number would be only "1e005" even when I want more digits. This is undesirable since I need all numbers to be the same length.
Is there a "pythonic" way to do this without resorting to complicated string operations?
precision = 2
number_to_convert = 10000
print "%0.*e"%(precision,number_to_convert)
is that what you are asking for?
You can use the %e string formatter:
>>> '%1.5e'%1234.567
'1.23457e+03'
>>> "%1.5e"%100000
'1.00000e+05'
%x.ye where x = min characters and y = max precision.
If you need to keep the 3-digit exponent like in your example, you can define your own function. Here's an example adapted from this answer:
def eformat(f, prec, exp_digits):
s = "%.*e"%(prec, f)
mantissa, exp = s.split('e')
return "%se%0*d"%(mantissa, exp_digits, int(exp))
>>> print eformat(1234.567, 4, 3)
1.2346e003

Categories

Resources