Related
I am facing a strange behavior of the round() function:
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", round(n))
This code prints:
0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
I expected the floating values to be always rounded up, but instead, it is rounded to the nearest even number.
Why such behavior, and what is the best way to get the correct result?
I tried to use the fractions but the result is the same.
The Numeric Types section documents this behaviour explicitly:
round(x[, n])
x rounded to n digits, rounding half to even. If n is omitted, it defaults to 0.
Note the rounding half to even. This is also called bankers rounding; instead of always rounding up or down (compounding rounding errors), by rounding to the nearest even number you average out rounding errors.
If you need more control over the rounding behaviour, use the decimal module, which lets you specify exactly what rounding strategy should be used.
For example, to round up from half:
>>> from decimal import localcontext, Decimal, ROUND_HALF_UP
>>> with localcontext() as ctx:
... ctx.rounding = ROUND_HALF_UP
... for i in range(1, 15, 2):
... n = Decimal(i) / 2
... print(n, '=>', n.to_integral_value())
...
0.5 => 1
1.5 => 2
2.5 => 3
3.5 => 4
4.5 => 5
5.5 => 6
6.5 => 7
For example:
from decimal import Decimal, ROUND_HALF_UP
Decimal(1.5).quantize(0, ROUND_HALF_UP)
# This also works for rounding to the integer part:
Decimal(1.5).to_integral_value(rounding=ROUND_HALF_UP)
You can use this:
import math
def normal_round(n):
if n - math.floor(n) < 0.5:
return math.floor(n)
return math.ceil(n)
It will round number up or down properly.
round() will round either up or down, depending on if the number is even or odd. A simple way to only round up is:
int(num + 0.5)
If you want this to work properly for negative numbers use:
((num > 0) - (num < 0)) * int(abs(num) + 0.5)
Note, this can mess up for large numbers or really precise numbers like 5000000000000001.0 and 0.49999999999999994.
Love the fedor2612 answer. I expanded it with an optional "decimals" argument for those who want to use this function to round any number of decimals (say for example if you want to round a currency $26.455 to $26.46).
import math
def normal_round(n, decimals=0):
expoN = n * 10 ** decimals
if abs(expoN) - abs(math.floor(expoN)) < 0.5:
return math.floor(expoN) / 10 ** decimals
return math.ceil(expoN) / 10 ** decimals
oldRounding = round(26.455,2)
newRounding = normal_round(26.455,2)
print(oldRounding)
print(newRounding)
Output:
26.45
26.46
The behavior you are seeing is typical IEEE 754 rounding behavior. If it has to choose between two numbers that are equally different from the input, it always picks the even one. The advantage of this behavior is that the average rounding effect is zero - equally many numbers round up and down. If you round the half way numbers in a consistent direction the rounding will affect the expected value.
The behavior you are seeing is correct if the objective is fair rounding, but that is not always what is needed.
One trick to get the type of rounding you want is to add 0.5 and then take the floor. For example, adding 0.5 to 2.5 gives 3, with floor 3.
Why make it so complicated? (Only works for positive numbers)
def HalfRoundUp(value):
return int(value + 0.5)
You could of course make it into a lambda which would be:
HalfRoundUp = lambda value: int(value + 0.5)
Unfortunately, this simple answer doesn't work with negative numbers, but it can be fixed with the floor function from math: (This works for both positive and negative numbers too)
from math import floor
def HalfRoundUp(value):
floor(value + 0.5)
Short version: use the decimal module. It can represent numbers like 2.675 precisely, unlike Python floats where 2.675 is really 2.67499999999999982236431605997495353221893310546875 (exactly). And you can specify the rounding you desire: ROUND_CEILING, ROUND_DOWN, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_UP, and ROUND_05UP are all options.
In the question this is basically an issue when dividing a positive integer by 2. The easisest way is int(n + 0.5) for individual numbers.
However we cannot apply this to series, therefore what we then can do for example for a pandas dataframe, and without going into loops, is:
import numpy as np
df['rounded_division'] = np.where(df['some_integer'] % 2 == 0, round(df['some_integer']/2,0), round((df['some_integer']+1)/2,0))
A small addition as the rounding half up with some of the solutions might not work as expected in some cases.
Using the function from above for instance:
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
round_half_up(1.35, 1)
1.4
round_half_up(4.35, 1)
4.3
Where I was expecting 4.4. What did the trick for me was converting x into a string first.
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(x: float, num_decimals: int) -> float:
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(str(x)).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
round_half_up(4.35, 1)
4.4
Rounding to the nearest even number has become common practice in numerical disciplines. "Rounding up" produces a slight bias towards larger results.
So, from the perspective of the scientific establishment, round has the correct behavior.
Here is another solution.
It will work as normal rounding in excel.
from decimal import Decimal, getcontext, ROUND_HALF_UP
round_context = getcontext()
round_context.rounding = ROUND_HALF_UP
def c_round(x, digits, precision=5):
tmp = round(Decimal(x), precision)
return float(tmp.__round__(digits))
c_round(0.15, 1) -> 0.2, c_round(0.5, 0) -> 1
The following solution achieved "school fashion rounding" without using the decimal module (which turns out to be slow).
def school_round(a_in,n_in):
''' python uses "banking round; while this round 0.05 up" '''
if (a_in * 10 ** (n_in + 1)) % 10 == 5:
return round(a_in + 1 / 10 ** (n_in + 1), n_in)
else:
return round(a_in, n_in)
e.g.
print(round(0.005,2)) # 0
print(school_round(0.005,2)) #0.01
So just to make sure there is a crystal clear working example here, I wrote a small convenience function
def round_half_up(x: float, num_decimals: int) -> float:
"""Use explicit ROUND HALF UP. See references, for an explanation.
This is the proper way to round, as taught in school.
Args:
x:
num_decimals:
Returns:
https://stackoverflow.com/questions/33019698/how-to-properly-round-up-half-float-numbers-in-python
"""
if num_decimals < 0:
raise ValueError("Num decimals needs to be at least 0.")
target_precision = "1." + "0" * num_decimals
rounded_x = float(Decimal(x).quantize(Decimal(target_precision), ROUND_HALF_UP))
return rounded_x
And an appropriate set of test cases
def test_round_half_up():
x = 1.5
y = round_half_up(x, 0)
assert y == 2.0
y = round_half_up(x, 1)
assert y == 1.5
x = 1.25
y = round_half_up(x, 1)
assert y == 1.3
y = round_half_up(x, 2)
assert y == 1.25
This is a function that takes the number of decimal places as an argument.
It also rounds up half decimal.
import math
def normal_round(n, decimal_places):
if int((str(n)[-1])) < 5:
return round(n, decimal_places)
return round(n + 10**(-1 * (decimal_places+1)), decimal_places)
Test cases:
>>> normal_round(5.12465, 4)
5.1247
>>> normal_round(5.12464, 4)
5.1246
>>> normal_round(5.12467, 4)
5.1247
>>> normal_round(5.12463, 4)
5.1246
>>> normal_round(5.1241, 4)
5.1241
>>> normal_round(5.1248, 4)
5.1248
>>> normal_round(5.1248, 3)
5.125
>>> normal_round(5.1242, 3)
5.124
You can use:
from decimal import Decimal, ROUND_HALF_UP
for i in range(1, 15, 2):
n = i / 2
print(n, "=>", Decimal(str(n)).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
A classical mathematical rounding without any libraries
def rd(x,y=0):
''' A classical mathematical rounding by Voznica '''
m = int('1'+'0'*y) # multiplier - how many positions to the right
q = x*m # shift to the right by multiplier
c = int(q) # new number
i = int( (q-c)*10 ) # indicator number on the right
if i >= 5:
c += 1
return c/m
Compare:
print( round(0.49), round(0.51), round(0.5), round(1.5), round(2.5), round(0.15,1)) # 0 1 0 2 2 0.1
print( rd(0.49), rd(0.51), rd(0.5), rd(1.5), rd(2.5), rd(0.15,1)) # 0 1 1 2 3 0.2
Knowing that round(9.99,0) rounds to int=10 and int(9.99) rounds to int=9 brings success:
Goal: Provide lower and higher round number depending on value
def get_half_round_numers(self, value):
"""
Returns dict with upper_half_rn and lower_half_rn
:param value:
:return:
"""
hrns = {}
if not isinstance(value, float):
print("Error>Input is not a float. None return.")
return None
value = round(value,2)
whole = int(value) # Rounds 9.99 to 9
remainder = (value - whole) * 100
if remainder >= 51:
hrns['upper_half_rn'] = round(round(value,0),2) # Rounds 9.99 to 10
hrns['lower_half_rn'] = round(round(value,0) - 0.5,2)
else:
hrns['lower_half_rn'] = round(int(value),2)
hrns['upper_half_rn'] = round(int(value) + 0.5,2)
return hrns
Some testing:
yw
import math
# round tossing n digits from the end
def my_round(n, toss=1):
def normal_round(n):
if isinstance(n, int):
return n
intn, dec = str(n).split(".")
if int(dec[-1]) >= 5:
if len(dec) == 1:
return math.ceil(n)
else:
return float(intn + "." + str(int(dec[:-1]) + 1))
else:
return float(intn + "." + dec[:-1])
while toss >= 1:
n = normal_round(n)
toss -= 1
return n
for n in [1.25, 7.3576, 30.56]:
print(my_round(n, 2))
1.0
7.36
31
import math
def round_half_up(x: float) -> int:
if x < 0:
return math.trunc(x) if -x % 1 < 0.5 else math.floor(x)
else:
return math.trunc(x) if x % 1 < 0.5 else math.ceil(x)
This even works for corner cases like 0.49999999999999994 and 5000000000000001.0.
You can try this
def round(num):
return round(num + 10**(-9))
it will work since num = x.5 will always will be x.5 + 0.00...01 in the process which its closer to x+1 hence the round function will work properly and it will round x.5 to x+1
I'm trying to create a symbolic matrix (S) of general size (let's say LxL), and I want to set each element of the matrix as a function of the indices, i.e.:
S[m,n] = (u+i/2*(n-m))/(u-i/2*(n-m)) * (u+i/2*(n+m))/(u-i/2*(n+m))
I tried running this in sympy, and I got
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-a456d47e99e7> in <module>()
2 S_l = MatrixSymbol('S_l',2*l+1,2*l+1)
3 S_k = MatrixSymbol('S_k',2*k+1,2*k+1)
----> 4 S_l[m,n] = (u+i/2*(n-m))/(u-i/2*(n-m)) * (u+i/2*(n+m))/(u-i/2*(n+m))
TypeError: 'MatrixSymbol' object does not support item assignment
Searching through Stack Exchange I found this question from last year:
Sympy - Dense Display of Matrices of Arbitrary Size
Which is unanswered and not exactly the same. Is it the same issue, or am I just trying to do an impossible thing in sympy (or computers in general)?
I know this is ancient, but I came across the same issue and figured I'd share a solution that works for me. You'll need to use a FunctionMatrix object instead of a MatrixSymbol. For background, I'm using SymPy 1.6.1 on Python 3.5.2.
Here's an example. Using the code below, I've setup some iteration symbols and the function f(i,j) I'd like to use for the elements of my matrix u.
# Import SymPy for symbolic computations
import sympy as sym
# Index variables
i,j = sym.symbols('i j', integer=True);
N = sym.Symbol('N', real=True, integer=True, zero=False, positive=True);
# The function we'll use for our matrix
def f(i,j):
# Some arbitrary function...
return i + j;
# Define a function matrix where elements of the matrix
# are a function of the indices
U = sym.FunctionMatrix(N, N, sym.Lambda((i,j), f(i,j)));
Now, let's try using the elements in the matrix by summing them all up...
U_sum = sym.Sum(u[i,j], (i, 0, N), (j, 0, N));
U_sum
>>>
N N
___ ___
╲ ╲
╲ ╲
╱ ╱ (i + j)
╱ ╱
‾‾‾ ‾‾‾
j = 0 i = 0
Then, let's tell SymPy to calculate the summation
our_sum.doit().simplify()
>>> N * ( N**2 + 2*N + 1 )
This certainly can be done. The docs offer some examples. Here's one
>>> Matrix(3, 4, lambda i,j: 1 - (i+j) % 2)
Matrix([
[1, 0, 1, 0],
[0, 1, 0, 1],
[1, 0, 1, 0]])
Today is my first day with Swift, and I have run into a problem. I am using rand to generate a random number, but it is giving me the same results every time I run the code.
main.swift:
import Foundation
var player = Player()
for _ in 1..6 {
println(player.kick())
}
player.swift:
import Foundation
class Player {
var health = 25
var xp = 15
var upgrades = ["kick": 0, "punch": 0]
func kick() -> Int {
let range = (3, 7)
let damage = Int(rand()) % (range.1 - range.0) + range.0 + 1
return damage
}
func punch() -> Int {
let range = (4, 6)
let damage = Int(rand()) % (range.1 - range.0) + range.0 + 1
return damage
}
}
Every time I run the code, it logs these numbers:
7
5
5
6
6
I also tried this: Int(arc4random(range.1 - range.0)) + range.0 + 1 but it said it couldn't find an overload for + that accepts the supplied arguments
I have no idea why this would be happening. I'd appreciate some help, thanks!
You should never use rand(), use arc4random - it's a much better generator. If you check its man-pages, you'll find that it has an integer range generator form called arc4random_uniform(), which you should use to avoid modulo bias when the modulus is not a power of 2. I believe the following is what you want, it worked for me in playground:
let damage = arc4random_uniform(UInt32(range.1 - range.0) + 1) + UInt32(range.0)
The + 1 is because the upper end of arc4random_uniform() is non-inclusive. If your range is (4,7), this should give occurrences of 4, 5, 6, and 7.
rand() in most programming environments gives you a repeatable sequence of pseudo-random numbers, by design. Look for a function called seed or srand for ways to initialize the random number generator.
Using rand() is fine, you can seed the pseudo-random number generator with this call at the beginning of your program:
srand(UInt32(time(nil)))
I am given a function rand5() that generates, with a uniform distribution, a random integer in the closed interval [1,5]. How can I use rand5(), and nothing else, to create a function rand7(), which generates integers in [1,7] (again, uniformly distributed) ?
I searched stackoverflow, and found many similar questions, but not exactly like this one.
My initial attempt was rand5() + 0.5*rand5() + 0.5*rand5(). But this won't generate integers from 1 to 7 with uniform probability. Any answers, or links to answers, are very welcome.
Note that a prefect uniform distribution cannot be achieved with a bounded number of draw5() invocations, because for every k: 5^k % 7 != 0 - so you will always have some "spare" elements.
Here is a solution with unbounded number of draw5() uses:
Draw two numbers, x1,x2. There are 5*5=25 possible outcomes for this.
Note that 25/7 ~= 3.57. Chose 3*7=21 combinations, such that each combination will be mapped to one number in [1,7], for all other 4 numbers - redraw.
For example:
(1,1),(1,2),(2,1) : 1
(3,1),(1,3),(3,2): 2
(3,3),(1,4),(4,1): 3
(2,4),(4,2)(3,4): 4
(4,3), (4,4), (1,5): 5
(5,1), (2,5), (5,2) : 6
(5,3), (3,5), (4,5) : 7
(5,4),(5,5),(2,3), (2,2) : redraw
Here's a simple way:
Use rand5() to generate a sequence of three random integers from the set { 1, 2, 4, 5 } (i.e., throw away any 3 that is generated).
If all three numbers are in the set { 1, 2 }, discard the sequence and return to step 1.
For each number in the sequence, map { 1, 2} to 0 and { 4, 5 } to 1. Use these as the three bit values for a 3-bit number. Because the bits cannot all be 0, the number will be in the range [1, 7]. Because each bit is 0 or 1 with equal probability, the distribution over [1, 7] should be uniform.
ok I had to think about it for a while but it is actually not that hard. Imagine instead of rand5 you had rand2 which either outputs 0 or 1. You can make rand2 our of rand5 by simply doing
rand2() {
if(rand5() > 2.5) return 1
else return 0
}
now using rand2 multiple times do a tree to get rand7. For example if you start rand7 can be in [1,2,3,4,5,6,7] after a throw of rand2 which gives 0 you now subset to [1,2,3,4] and after another throw or rand2 which is 1 you subset to [3,4] and a final throw of 1 gives the output of rand7 to be 4. In general this tree trick can work to take a rand2 and map to randx where x is any integer.
Here's one meta-trick which comes in handy for lots of these problems: the bias is introduced when we treat the terms differently in some fashion, so if we treat them all the same at each step and perform operations only on the set, we'll stay out of trouble.
We have to call rand5() at least once (obviously!), but if we branch on that bad things happen unless we're clever. So instead let's call it once for each of the 7 possibilities:
In [126]: import random
In [127]: def r5():
.....: return random.randint(1, 5)
.....:
In [128]: [r5() for i in range(7)]
Out[128]: [3, 1, 3, 4, 1, 1, 2]
Clearly each of these terms was equally likely to be any of these numbers.. but only one of them happened to be 2, so if our rule had been "choose whichever term rand5() returns 2 for" then it would have worked. Or 4, or whatever, and if we simply looped long enough that would happen. So there are lots of way to come up with something that works. Here (in pseudocode -- this is terrible Python) is one way:
import random, collections
def r5():
return random.randint(1, 5)
def r7():
left = range(1, 8)
while True:
if len(left) == 1:
return left[0]
rs = [r5() for n in left]
m = max(rs)
how_many_at_max = rs.count(m)
if how_many_at_max == len(rs):
# all the same: try again
continue
elif how_many_at_max == 1:
# hooray!
return left[rs.index(m)]
# keep only the non-maximals
left = [l for l,r in zip(left, rs) if r != m]
which gives
In [189]: collections.Counter(r7() for _ in xrange(10**6))
Out[189]: Counter({7: 143570, 5: 143206, 4: 142827, 2: 142673, 6: 142604, 1: 142573, 3: 142547})
I know that I can generate random floats with rand(max). I tried to generate a float in a range, this shouldn't be hard. But e.g rand(1.4512) returns 0, thus rand isn't calculating with floats. Now I tried a little trick, converting the thing to an integer and after randomizing a fitting number in my desired range, calculating it back to a float.. which is not working.
My question is how to do this in a better way. If there is no better way, why is this one not working? (Maybe it's too late for me, I should've started sleeping 2 hours ago..). The whole thing aims to be a method for calculating a "position" field for database records so users can order them manually. I've never done something like this before, maybe someone can hint me with a better solution.
Here's the code so far:
def calculate_position(#elements, index)
min = #elements[index].position
if #elements[index + 1].nil?
pos = min + 1
else
pos = min + (rand(#elements[index + 1].position * 10000000000) / 10000000000)
end
return pos
end
Pass a range of floats to rand
If you want to "create a random float in a range between two floats", just pass a range of floats to rand.
rand(11.2...76.9)
(Tested with Ruby 2.1)
Edit
According to the documentation: https://ruby-doc.org/core-2.4.0/Random.html
There are two different ways to write the random function: inclusive and exclusive for the last value
rand(5..9) # => one of [5, 6, 7, 8, 9]
rand(5...9) # => one of [5, 6, 7, 8]
rand(5.0..9.0) # => between 5.0 and 9.0, including 9.0
rand(5.0...9.0) # => between 5.0 and 9.0, excluding 9.0
Let's recap:
rand() will generate a (psuedo-)random
float between 0 and 1.
rand(int) will generate a
(psuedo-)random integer between 0 and
int.
So something like:
def range (min, max)
rand * (max-min) + min
end
Should do nicely.
Update:
Just tested with a little unit test:
def testRange
min = 1
max = 100
1_000_000.times {
result = range min, max
print "ERROR" if result < min || result > max
}
end
Looks fine.
In 1.9 and 2.0 you can give a range argument to rand:
irb(main):001:0> 10.times { puts rand Math::E..Math::PI }
3.0656267148715446
2.7813979580609587
2.7661725184200563
2.9745784681934655
2.852157154320737
2.741063222095785
2.992638029938756
3.0713152547478866
2.879739743508003
2.7836491029737407
=> 10
I think your best bet is to use rand() to generate a random float between 0 and 1, and then multiply to set the range and add to set the offset:
def float_rand(start_num, end_num=0)
width = end_num-start_num
return (rand*width)+start_num
end
Note: since the order of the terms doesn't matter, making end_num default to 0 allows you to get a random float between 0 and x with float_rand(x).