I usually use
x = round(x, 3)
to round a number to the precision of 3 digits. Now I have this array:
[-1.10882605e-04 -2.01874994e-05 3.24209095e-05 -1.56917988e-05
-4.61406358e-05 1.99080610e-05 7.04079594e-05 2.64600122e-05
-3.53022316e-05 1.50542793e-05]
And using the same code just flattens everything down to 0. What I would like to have though is a function that gives me the most significant 3 digits rounded like it usually works for numbers larger than 1. Like this:
special_round(0.00034567, 3)
=
0.000346
Any idea how this could be done? Thanks!
Here is a solution that figures out the order of magnitude and does an elment wise rounding.
Note that this will only work correctly for values < 1 and > -1, which I guess is a valid assumption regarding your example data.
import numpy as np
a = np.array([-1.10882605e-04, -2.01874994e-05, 3.24209095e-05, -1.56917988e-05,
-4.61406358e-05, 1.99080610e-05, 7.04079594e-05 , 2.64600122e-05,
-3.53022316e-05 , 1.50542793e-05])
def special_round(vec):
exponents = np.floor(np.log10(np.abs(vec))).astype(int)
return np.stack([np.round(v, decimals=-e+3) for v, e in zip(vec, exponents)])
b = special_round(a)
>>> array([-1.109e-04, -2.019e-05, 3.242e-05, -1.569e-05, -4.614e-05,
1.991e-05, 7.041e-05, 2.646e-05, -3.530e-05, 1.505e-05])
Problem is, numbers you provided are starting to be so small that you are approaching limit of floating point precision, thus some artifacts show up seemingly for no reason.
def special_round(number, precision):
negative = number < 0
number = abs(number)
i = 0
while number <= 1 or number >= 10:
if number <= 1:
i += 1
number *= 10
else:
i += -1
number /= 10
rounded = round(number, precision)
if negative:
rounded = -rounded
return rounded * (10 ** -i)
Output:
[-0.0001109, -2.019e-05, 3.2420000000000005e-05, -1.569e-05, -4.614e-05, 1.9910000000000004e-05, 7.041000000000001e-05, 2.646e-05, -3.5300000000000004e-05, 1.505e-05]
You can do so by creating a specific function using the math package:
from math import log10 , floor
import numpy as np
def round_it(x, sig):
return round(x, sig-int(floor(log10(abs(x))))-1)
a = np.array([-1.10882605e-04, -2.01874994e-05, 3.24209095e-05, -1.56917988e-05,
-4.61406358e-05, 1.99080610e-05, 7.04079594e-05, 2.64600122e-05,
-3.53022316e-05, 1.50542793e-05])
round_it_np = np.vectorize(round_it) # vectorize the function to apply on numpy array
round_it_np(a, 3) # 3 is rounding with 3 significant digits
This will result in
array([-1.11e-04, -2.02e-05, 3.24e-05, -1.57e-05, -4.61e-05, 1.99e-05,
7.04e-05, 2.65e-05, -3.53e-05, 1.51e-05])
Here is a solution:
from math import log10, ceil
def special_round(x, n) :
lx = log10(abs(x))
if lx >= 0 : return round(x, n)
return round(x, n-ceil(lx))
for x in [10.23456, 1.23456, 0.23456, 0.023456, 0.0023456] :
print (x, special_round(x, 3))
print (-x, special_round(-x, 3))
Output:
10.23456 10.235
-10.23456 -10.235
1.23456 1.235
-1.23456 -1.235
0.23456 0.235
-0.23456 -0.235
0.023456 0.0235
-0.023456 -0.0235
0.0023456 0.00235
-0.0023456 -0.00235
You can use the common logarithm (provided by the built-in math module) to calculate the position of the first significant digit in your number (with 2 representing the hundreds, 1 representing the tens, 0 representing the ones, -1 representing the 0.x, -2 representing the 0.0x and so on...). Knowing the position of the first significant digit, you can use it to properly round the number.
import math
def special_round(n, significant_digits=0):
first_significant_digit = math.ceil((math.log10(abs(n))))
round_digits = significant_digits - first_significant_digit
return round(n, round_digits)
>>> special_round(0.00034567, 3)
>>> 0.000346
Related
I'm trying to help someone calculate the value of 4^3e9.
The problem is that most software don't support numbers this large. Is there an alternative way to calculate this?
My first attempt is to try to divide the number as it is being calculated by looping from 1 to 3e9. If the intermediate result is > 10 then I divide it by it's power of 10, then add this to a variable. In the end I will have a floating point number and the power of 10.
import math
powerof10 = 0
powerof = int(3e9)
# print('powerof', powerof)
initial_value = 4
float_value = initial_value
for i in range(1,powerof): #start from 1 to get correct number of operations
float_value *= initial_value
# print('float value', float_value)
print(i)
if (float_value > 10):
powerof10increment = math.floor(math.log10(float_value))
# print('powerof10', powerof10increment)
powerof10 += powerof10increment
float_value /= 10**powerof10increment
# print('reduced float value', float_value)
print(float_value, ' x 10^', powerof10)
This based on this question here: I want to know what is the value of 4 to the power of 3000000000 (3e+9)
According to the question the number should be in the format 1 x 10^x, so I think only x is required.
Thanks to #NickODell for his comment!
import math
powerof10 = (3e9)*math.log(4)/math.log(10)
print(powerof10)
significand = 10**(powerof10 - math.floor(powerof10))
print(significand, 'x 10^', math.floor(powerof10))
# 9.63578180503111 x 10^ 1806179973
Which matches with the result I get from Wolfram Alpha.
I have a float numer
a = 1.263597
I hope get
b = 1.2635
But when I try
round (a,4)
then result is
1.2636
What should I do?
Try math.floor with this small modification -
import math
def floor_rounded(n,d):
return math.floor(n*10**d)/10**d
n = 1.263597
d = 4
output = floor_rounded(n,d)
print(output)
1.2635
For your example, you can just do math.floor(1.263597 * 10000)/10000
EDIT: Based on the valid comment by #Mark, here is another way of solving this, but this time forcing the custom rounding using string operations.
#EDIT: Alternate approach, based on the comment by Mark Dickinson
def string_rounded(n,d):
i,j = str(n).split('.')
return float(i+'.'+j[:d])
n = 8.04
d = 2
output = string_rounded(n,d)
output
8.04
Plain Python without importing any libraries (even not standard libraries):
def round_down(number, ndigits=None):
if ndigits is None or ndigits == 0:
# Return an integer if ndigits is 0
return int(number)
else:
return int(number * 10**ndigits) / 10**ndigits
a = 1.263597
b = round_down(a, 4)
print(b)
1.2635
Note that this function rounds towards zero, i.e. it rounds down positive floats and rounds up negative floats.
def round_down(number, ndigits=0):
return round(number-0.5/pow(10, ndigits), ndigits)
Run:
round_down(1.263597, 4)
>> 1.2635
How can I take a float variable, and control how far out the float goes without round()? For example.
w = float(1.678)
I want to take x and make the following variables out of it.
x = 1.67
y = 1.6
z = 1
If I use the respective round methods:
x = round(w, 2) # With round I get 1.68
y = round(y, 1) # With round I get 1.7
z = round(z, 0) # With round I get 2.0
It's going to round and alter the numbers to the point where there no use to me. I understand this is the point of round and its working properly. How would I go about getting the information that I need in the x,y,z variables and still be able to use them in other equations in a float format?
You can do:
def truncate(f, n):
return math.floor(f * 10 ** n) / 10 ** n
testing:
>>> f=1.923328437452
>>> [truncate(f, n) for n in range(7)]
[1.0, 1.9, 1.92, 1.923, 1.9233, 1.92332, 1.923328]
A super simple solution is to use strings
x = float (str (w)[:-1])
y = float (str (w)[:-2])
z = float (str (w)[:-3])
Any of the floating point library solutions would require you dodge some rounding, and using floor/powers of 10 to pick out the decimals can get a little hairy by comparison to the above.
Integers are faster to manipulate than floats/doubles which are faster than strings. In this case, I tried to get time with both approach :
timeit.timeit(stmt = "float(str(math.pi)[:12])", setup = "import math", number = 1000000)
~1.1929605630000424
for :
timeit.timeit(stmt = "math.floor(math.pi * 10 ** 10) / 10 ** 10", setup = "import math", number = 1000000)
~0.3455968870000561
So it's safe to use math.floor rather than string operation on it.
If you just need to control the precision in format
pi = 3.14159265
format(pi, '.3f') #print 3.142 # 3 precision after the decimal point
format(pi, '.1f') #print 3.1
format(pi, '.10f') #print 3.1415926500, more precision than the original
If you need to control the precision in floating point arithmetic
import decimal
decimal.getcontext().prec=4 #4 precision in total
pi = decimal.Decimal(3.14159265)
pi**2 #print Decimal('9.870') whereas '3.142 squared' would be off
--edit--
Without "rounding", thus truncating the number
import decimal
from decimal import ROUND_DOWN
decimal.getcontext().prec=4
pi*1 #print Decimal('3.142')
decimal.getcontext().rounding = ROUND_DOWN
pi*1 #print Decimal('3.141')
I think the easiest answer is :
from math import trunc
w = 1.678
x = trunc(w * 100) / 100
y = trunc(w * 10) / 10
z = trunc(w)
also this:
>>> f = 1.678
>>> n = 2
>>> int(f * 10 ** n) / 10 ** n
1.67
Easiest way to get integer:
series_col.round(2).apply(lambda x: float(str(x).split(".",1)[0]))
Here is the example which is bothering me:
>>> x = decimal.Decimal('0.0001')
>>> print x.normalize()
>>> print x.normalize().to_eng_string()
0.0001
0.0001
Is there a way to have engineering notation for representing mili (10e-3) and micro (10e-6)?
Here's a function that does things explicitly, and also has support for using SI suffixes for the exponent:
def eng_string( x, format='%s', si=False):
'''
Returns float/int value <x> formatted in a simplified engineering format -
using an exponent that is a multiple of 3.
format: printf-style string used to format the value before the exponent.
si: if true, use SI suffix for exponent, e.g. k instead of e3, n instead of
e-9 etc.
E.g. with format='%.2f':
1.23e-08 => 12.30e-9
123 => 123.00
1230.0 => 1.23e3
-1230000.0 => -1.23e6
and with si=True:
1230.0 => 1.23k
-1230000.0 => -1.23M
'''
sign = ''
if x < 0:
x = -x
sign = '-'
exp = int( math.floor( math.log10( x)))
exp3 = exp - ( exp % 3)
x3 = x / ( 10 ** exp3)
if si and exp3 >= -24 and exp3 <= 24 and exp3 != 0:
exp3_text = 'yzafpnum kMGTPEZY'[ ( exp3 - (-24)) / 3]
elif exp3 == 0:
exp3_text = ''
else:
exp3_text = 'e%s' % exp3
return ( '%s'+format+'%s') % ( sign, x3, exp3_text)
EDIT:
Matplotlib implemented the engineering formatter, so one option is to directly use Matplotlibs formatter, e.g.:
import matplotlib as mpl
formatter = mpl.ticker.EngFormatter()
formatter(10000)
result: '10 k'
Original answer:
Based on Julian Smith's excellent answer (and this answer), I changed the function to improve on the following points:
Python3 compatible (integer division)
Compatible for 0 input
Rounding to significant number of digits, by default 3, no trailing zeros printed
so here's the updated function:
import math
def eng_string( x, sig_figs=3, si=True):
"""
Returns float/int value <x> formatted in a simplified engineering format -
using an exponent that is a multiple of 3.
sig_figs: number of significant figures
si: if true, use SI suffix for exponent, e.g. k instead of e3, n instead of
e-9 etc.
"""
x = float(x)
sign = ''
if x < 0:
x = -x
sign = '-'
if x == 0:
exp = 0
exp3 = 0
x3 = 0
else:
exp = int(math.floor(math.log10( x )))
exp3 = exp - ( exp % 3)
x3 = x / ( 10 ** exp3)
x3 = round( x3, -int( math.floor(math.log10( x3 )) - (sig_figs-1)) )
if x3 == int(x3): # prevent from displaying .0
x3 = int(x3)
if si and exp3 >= -24 and exp3 <= 24 and exp3 != 0:
exp3_text = 'yzafpnum kMGTPEZY'[ exp3 // 3 + 8]
elif exp3 == 0:
exp3_text = ''
else:
exp3_text = 'e%s' % exp3
return ( '%s%s%s') % ( sign, x3, exp3_text)
The decimal module is following the Decimal Arithmetic Specification, which states:
This is outdated - see below
to-scientific-string – conversion to numeric string
[...]
The coefficient is first converted to a string in base ten using the characters 0 through 9 with no leading zeros (except if its value is zero, in which case a single 0 character is used).
Next, the adjusted exponent is calculated; this is the exponent, plus the number of characters in the converted coefficient, less one. That is, exponent+(clength-1), where clength is the length of the coefficient in decimal digits.
If the exponent is less than or equal to zero and the adjusted exponent is greater than or equal to -6, the number will be converted
to a character form without using exponential notation.
[...]
to-engineering-string – conversion to numeric string
This operation converts a number to a string, using engineering
notation if an exponent is needed.
The conversion exactly follows the rules for conversion to scientific
numeric string except in the case of finite numbers where exponential
notation is used. In this case, the converted exponent is adjusted to be a multiple of three (engineering notation) by positioning the decimal point with one, two, or three characters preceding it (that is, the part before the decimal point will range from 1 through 999).
This may require the addition of either one or two trailing zeros.
If after the adjustment the decimal point would not be followed by a digit then it is not added. If the final exponent is zero then no indicator letter and exponent is suffixed.
Examples:
For each abstract representation [sign, coefficient, exponent] on the left, the resulting string is shown on the right.
Representation
String
[0,123,1]
"1.23E+3"
[0,123,3]
"123E+3"
[0,123,-10]
"12.3E-9"
[1,123,-12]
"-123E-12"
[0,7,-7]
"700E-9"
[0,7,1]
"70"
Or, in other words:
>>> for n in (10 ** e for e in range(-1, -8, -1)):
... d = Decimal(str(n))
... print d.to_eng_string()
...
0.1
0.01
0.001
0.0001
0.00001
0.000001
100E-9
I realize that this is an old thread, but it does come near the top of a search for python engineering notation and it seems prudent to have this information located here.
I am an engineer who likes the "engineering 101" engineering units. I don't even like designations such as 0.1uF, I want that to read 100nF. I played with the Decimal class and didn't really like its behavior over the range of possible values, so I rolled a package called engineering_notation that is pip-installable.
pip install engineering_notation
From within Python:
>>> from engineering_notation import EngNumber
>>> EngNumber('1000000')
1M
>>> EngNumber(1000000)
1M
>>> EngNumber(1000000.0)
1M
>>> EngNumber('0.1u')
100n
>>> EngNumber('1000m')
1
This package also supports comparisons and other simple numerical operations.
https://github.com/slightlynybbled/engineering_notation
The «full» quote shows what is wrong!
The decimal module is indeed following the proprietary (IBM) Decimal Arithmetic Specification.
Quoting this IBM specification in its entirety clearly shows what is wrong with decimal.to_eng_string() (emphasis added):
to-engineering-string – conversion to numeric string
This operation converts a number to a string, using engineering
notation if an exponent is needed.
The conversion exactly follows the rules for conversion to scientific
numeric string except in the case of finite numbers where exponential
notation is used. In this case, the converted exponent is adjusted to be a multiple of three (engineering notation) by positioning the decimal point with one, two, or three characters preceding it (that is, the part before the decimal point will range from 1 through 999). This may require the addition of either one or two trailing zeros.
If after the adjustment the decimal point would not be followed by a digit then it is not added. If the final exponent is zero then no indicator letter and exponent is suffixed.
This proprietary IBM specification actually admits to not applying the engineering notation for numbers with an infinite decimal representation, for which ordinary scientific notation is used instead! This is obviously incorrect behaviour for which a Python bug report was opened.
Solution
from math import floor, log10
def powerise10(x):
""" Returns x as a*10**b with 0 <= a < 10
"""
if x == 0: return 0,0
Neg = x < 0
if Neg: x = -x
a = 1.0 * x / 10**(floor(log10(x)))
b = int(floor(log10(x)))
if Neg: a = -a
return a,b
def eng(x):
"""Return a string representing x in an engineer friendly notation"""
a,b = powerise10(x)
if -3 < b < 3: return "%.4g" % x
a = a * 10**(b % 3)
b = b - b % 3
return "%.4gE%s" % (a,b)
Source: https://code.activestate.com/recipes/578238-engineering-notation/
Test result
>>> eng(0.0001)
100E-6
Like the answers above, but a bit more compact:
from math import log10, floor
def eng_format(x,precision=3):
"""Returns string in engineering format, i.e. 100.1e-3"""
x = float(x) # inplace copy
if x == 0:
a,b = 0,0
else:
sgn = 1.0 if x > 0 else -1.0
x = abs(x)
a = sgn * x / 10**(floor(log10(x)))
b = int(floor(log10(x)))
if -3 < b < 3:
return ("%." + str(precision) + "g") % x
else:
a = a * 10**(b % 3)
b = b - b % 3
return ("%." + str(precision) + "gE%s") % (a,b)
Trial:
In [10]: eng_format(-1.2345e-4,precision=5)
Out[10]: '-123.45E-6'
I'm building a range between two numbers (floats) and I'd like this range to be of an exact fixed length (no more, no less). range and arange work with steps, instead. To put things into pseudo Python, this is what I'd like to achieve:
start_value = -7.5
end_value = 0.1
my_range = my_range_function(star_value, end_value, length=6)
print my_range
[-7.50,-5.98,-4.46,-2.94,-1.42,0.10]
This is essentially equivalent to the R function seq which can specify a sequence of a given length. Is this possible in Python?
Thanks.
Use linspace() from NumPy.
>>> from numpy import linspace
>>> linspace(-7.5, 0.1, 6)
array([-7.5 , -5.98, -4.46, -2.94, -1.42, 0.1])
>>> linspace(-7.5, 0.1, 6).tolist()
[-7.5, -5.9800000000000004, -4.46, -2.9399999999999995, -1.4199999999999999, 0.10000000000000001]
It should be the most efficient and accurate.
See Recipe 66472: frange(), a range function with float increments (Python) with various float implementations, their pros and cons.
Alternatively, if precision is important to you, work with decimal.Decimal instead of float (convert to and then back) as answered in Python decimal range() step value.
def my_function(start, end, length):
len = length - 1
incr = (end-start) / len
r = [ start ]
for i in range(len):
r.append ( r[i] + incr )
return r
How about this:
def my_range_function(start, end, length):
if length <= 1: return [ start ]
step = (end - start) / (length - 1)
return [(start + i * step) for i in xrange(length)]
For your sample range, it returns:
[-7.5, -5.9800000000000004, -4.46,
-2.9399999999999995, -1.4199999999999999, 0.099999999999999645]
Of course it's full of round errors, but that's what you get when working with floats.
In order to handle the rounding errors, the following code utilizes Python's decimal module. You can set the rounding; for this sample I've set it to two decimal points via round_setting = '.01'. In order to handle any rounding errors, the last step is adjusted to the remainder.
Code
#!/usr/bin/env python
# encoding: utf-8
from __future__ import print_function
import math
import decimal
start_value = -7.5
end_value = 0.1
num_of_steps = 6
def my_range(start_value, end_value, num_of_steps):
round_setting = '.01'
start_decimal = decimal.Decimal(str(start_value)).quantize(
decimal.Decimal(round_setting))
end_decimal = decimal.Decimal(str(end_value)).quantize(
decimal.Decimal(round_setting))
num_of_steps_decimal = decimal.Decimal(str(num_of_steps)).quantize(
decimal.Decimal(round_setting))
step_decimal = ((end_decimal - start_decimal) /
num_of_steps_decimal).quantize(decimal.Decimal(round_setting))
# Change the last step in case there are rounding errors
last_step_decimal = (end_decimal - ((num_of_steps - 1) * step_decimal) -
start_decimal).quantize(decimal.Decimal(round_setting))
print('Start value = ', start_decimal)
print('End value = ', end_decimal)
print('Number of steps = ', num_of_steps)
print('Normal step for range = ', step_decimal)
print('Last step used for range = ', last_step_decimal)
my_range(start_value, end_value, num_of_steps)
Output
$ ./fixed_range.py
Start value = -7.50
End value = 0.10
Number of steps = 6
Normal step for range = 1.27
Last step used for range = 1.25
From there you can use the normal step and the last step to create your list.