Floor/Ceil code still not working as required - rounding

A few weeks ago I was provided with a really useful piece of code to help me to round my health data outcomes to 1dp, using the principle of rounding 1.25 to 1.3 (Python 3 doesn't do this as standard). I've unfortunately come across an instance where my newly defined rounding rule isn't working though! Can someone please suggest an amendment to the my_round below please? It's so frustrating as I thought I'd got the perfect solution here.
import pandas as pd
import math
raw_data = {'AreaCode' : ['101', '101', '101'],
'Disaggregation' : ['1864', '65Over', 'Total'],
'Numerator' : [19.0, 82.0, 101.0],
'Denominator' : [24.0, 160.0, 184.0]}
Data = pd.DataFrame(raw_data, columns = ['AreaCode', 'Disaggregation', 'Numerator', 'Denominator'])
def my_round(n, ndigits=1):
try:
part = n * 10 ** ndigits
delta = part - int(part)
# always round "away from 0"
if delta >= 0.5 or -0.5 < delta <= 0:
part = math.ceil(part)
else:
part = math.floor(part)
val = part/(10 ** ndigits)
except ValueError:
val = np.nan
return val
Data['Outcome'] = (Data['Numerator'] / (Data['Denominator'])*100).apply(my_round)
When all this is run you can see that the 65Over outcome has rounded to 51.2 when the calculation is 82.0 / 160.0 * 100 = 51.250000. I have to be certain such instances will output as 51.3 in my published data.

You are probably encountering 'half-even rounding' or 'bankers rounding'. 51.25 will round to 51.2, 51.35 will round to 51.4. It's very common in computing to prevent aggregation errors. You can customize this, though. See How to properly round up half float numbers in Python?
In addition, your code is likely not working because it requires exact-equality between floats for the delta >= 0.5 bit to work when delta is exactly 0.5. The division by 10 is probably giving you loss-of-precision such that you can't subsequently get exact equality.
Edit: Your issue is that the original calculation, 82.0 / 160.0 * 100, gives you an answer that is no longer exactly 51.25 due to loss-of-precision due to floating-point division/multiplication. Multiplying first such that numbers remain integers will help. But really, you need to use Decimal (https://docs.python.org/3/library/decimal.html) module to ensure that base-ten arithmetic gives you 'natural answers'.

Related

Python setting Decimal Place range without rounding using truncate does not work for some numbers

I wish to take in a float variable,
e.g.
w = float(1.678)
and control how far out the float goes without round(), e.g.
x = 1.67
y = 1.6
z = 1
Which was already answered in Python setting Decimal Place range without rounding? and it worked well until one day this number 0.00225 popped up and it some how just doesn't work for this specific number
In [161]: truncate(0.00225, 5)
Out[161]: 0.00224
0.00224 and 0.00226 also gave similar issues.
Here's the output I got while testing from 0.00223 - 0.00227
In [159]: truncate(0.00223, 5)
Out[159]: 0.00223
In [160]: truncate(0.00224, 5)
Out[160]: 0.00223
In [161]: truncate(0.00225, 5)
Out[161]: 0.00224
In [162]: truncate(0.00226, 5)
Out[162]: 0.00225
In [163]: truncate(0.00227, 5)
Out[163]: 0.00227
Why does this happen for some numbers and how do I fix it?
(I mainly use this for a trading algo which requires inputs to have precise decimals)
This is not a simple problem and it has no simple solution. I would start by studying this article:
https://realpython.com/python-rounding/

Python3: The Fraction

from fractions import Fraction
from functools import reduce
def product(fracs): ## fracs is a list of Fraction objects from the subsequent function call
t = Fractions(reduce(lambda x,y: x.numerator * y.numerator,fracs), reduce(lambda x,y: x.denominator * y.denominator, fracs))
return t.numerator, t.denominator
if __name__ == '__main__':
fracs = []
for _ in range(int(input())):
fracs.append(Fraction(*map(int, input().split())))
result = product(fracs)
print(*result)
I'm trying to multiply a series of fractions together using Python3 functool's Fraction function. The problem i have is with the denominator perimeter for the t variable in the product(fracs) function. Upon testing with the following test case:
3
1 2
3 4
10 6
5 1
The output was 5 1. The numerator seem to work fine but broke down for the denominator. I am aware and have found alternative solutions to my problem however i am hoping to get this mystery solved. I've ran it through python tutor but i couldn't decipher the code's behavior.
I think it's how you are using reduce. The first time it's called you are passing two Fraction objects:
Fraction(1,2).denominator * Fraction(3,4).denominator
which returns 8 and is what you expect. However, the reduce function is not making this 8 a fraction so the next call to reduce looks like this:
8.denominator * Fraction(10,6).denominator
Which is 6 not the expected 48. This problem doesn't exist for the numerator because for an Int X:
X = X.numerator
Therefore you get 30 in the numerator and 6 in the denominator which reduces to 5.
I'm not familiar with the Fraction class but it seems like you might be reinventing the wheel a bit.
I suspect you can just multiply the fraction objects and it's multiplication operator is overloaded:
def product(fracs):
return reduce(lambda x,y: x * y, fracs)

Change the precision of torch.sigmoid?

I want my sigmoid to never print a solid 1 or 0, but to actually print the exact value
i tried using
torch.set_printoptions(precision=20)
but it didn't work. here's a sample output of the sigmoid function :
before sigmoid : tensor([[21.2955703735]])
after sigmoid : tensor([[1.]])
but i don't want it to print 1, i want it to print the exact number, how can i force this?
The difference between 1 and the exact value of sigmoid(21.2955703735) is on the order of 5e-10, which is significantly less than machine epsilon for float32 (which is about 1.19e-7). Therefore 1.0 is the best approximation that can be achieved with the default precision. You can cast your tensor to a float64 (AKA double precision) tensor to get a more precise estimate.
torch.set_printoptions(precision=20)
x = torch.tensor([21.2955703735])
result = torch.sigmoid(x.to(dtype=torch.float64))
print(result)
which results in
tensor([0.99999999943577644324], dtype=torch.float64)
Keep in mind that even with 64-bit floating point computation this is only accurate to about 6 digits past the last 9 (and will be even less precise for larger sigmoid inputs). A better way to represent numbers very close to one is to directly compute the difference between 1 and the value. In this case 1 - sigmoid(x) which is equivalent to 1 / (1 + exp(x)) or sigmoid(-x). For example,
x = torch.tensor([21.2955703735])
delta = torch.sigmoid(-x.to(dtype=torch.float64))
print(f'sigmoid({x.item()}) = 1 - {delta.item()}')
results in
sigmoid(21.295570373535156) = 1 - 5.642236648842976e-10
and is a more accurate representation of your desired result (though still not exact).

Why is a whole number not the same when rounded in a custom function?

I have the following custom function that rounds a number to a user-specified accuracy.
It is based on the general formula:
ROUND(Value/ Accuracy,0)*Accuracy
There are times where Number/Accuracy is exactly a multiple of 0.5, and Excel does not do the common rounding rule (ODD number - Round up, EVEN number - Round down), so I made a custom function.
Function CheckTemp(val As Range, NumAccuracy As Range) As Double
Dim Temp As Double
Temp= Abs(val) / NumAccuracy
CheckTemp = (Temp / 0.5) - WorksheetFunction.RoundDown(Temp / 0.5 , 0)
End Function
If CheckTemp = 0, then 'val' falls under this case where depending on the number, I want to specifically round down or up. If it is false, then the general Round() command is used.
I do have a weird case when Accuracy = 0.1 and any 'val' that meets the requirement:
#.X5000000...,
where: 'X' is an ODD number, or zero (i.e. 0,1,3,5,7,9).
Depending on the whole number, the function does not work.
Example:
val = - 5 361 202.55
NumAccuracy = 0.1
Temp = 53 612 025.5
Temp / 0.5 = 107 224 051.
WorksheetFunction.RoundDown(Temp / 0.5,0) = 107 224 051.
CheckTemp = -1.49012E-08
If I break this check into two separate functions, one to output (Temp/0.5) and WF.RoundDown(Temp / 0.5) to the Excel worksheet, and then subtract the two in the worksheet I get EXACTLY 0.
However with VBA coding, an error comes into play and results in a non-zero answer (even more worrisome a NEGATIVE value, which should be impossible when Temp is always positive, and RoundDown('x','y') will always result in a smaller number than 'x').
'val' can be a very large number with many decimal places, so I am trying to keep the 'Double' parameter if possible.
I tried 'Single' variable type and it seems to remove the error with CheckTemp(), but I am worried an end-user may use a number that exceeds the 'Single' variable limit.
You are not wrong, but native rounding in VBA is severely limited.
So, use a proper rounding function like RoundMid as found in my project VBA.Round. It uses Decimal if possible to avoid such errors.
Example:
Value = 5361202.55
NumAccuracy = 0.1
RoundedValue = RoundMid(Value / NumAccuracy, 0) * Numaccuracy
RoundedValue -> 5361202.6

problem with rounding in calculating minimum amount of coins in change (python)

I have a homework assignment in which I have to write a program that outputs the change to be given by a vending machine using the lowest number of coins. E.g. £3.67 can be dispensed as 1x£2 + 1x£1 + 1x50p + 1x10p + 1x5p + 1x2p.
However, I'm not getting the right answers and suspect that this might be due to a rounding problem.
change=float(input("Input change"))
twocount=0
onecount=0
halfcount=0
pttwocount=0
ptonecount=0
while change!=0:
if change-2>=0:
change=change-2
twocount+=1
else:
if change-1>=0:
change=change-1
onecount+=1
else:
if change-0.5>=0:
change=change-0.5
halfcount+=1
else:
if change-0.2>=0:
change=change-0.2
pttwocount+=1
else:
if change-0.1>=0:
change=change-0.1
ptonecount+=1
else:
break
print(twocount,onecount,halfcount,pttwocount,ptonecount)
RESULTS:
Input: 2.3
Output: 10010
i.e. 2.2
Input: 3.4
Output: 11011
i.e. 3.3
Some actually work:
Input: 3.2
Output: 11010
i.e. 3.2
Input: 1.1
Output: 01001
i.e. 1.1
Floating point accuracy
Your approach is correct, but as you guessed, the rounding errors are causing trouble. This can be debugged by simply printing the change variable and information about which branch your code took on each iteration of the loop:
initial value: 3.4
taking a 2... new value: 1.4
taking a 1... new value: 0.3999999999999999 <-- uh oh
taking a 0.2... new value: 0.1999999999999999
taking a 0.1... new value: 0.0999999999999999
1 1 0 1 1
If you wish to keep floats for output and input, multiply by 100 on the way in (cast to integer with int(round(change))) and divide by 100 on the way out of your function, allowing you to operate on integers.
Additionally, without the 5p, 2p and 1p values, you'll be restricted in the precision you can handle, so don't forget to add those. Multiplying all of your code by 100 gives:
initial value: 340
taking a 200... new value: 140
taking a 100... new value: 40
taking a 20... new value: 20
taking a 20... new value: 0
1 1 0 2 0
Avoid deeply nested conditionals
Beyond the decimal issue, the nested conditionals make your logic very difficult to reason about. This is a common code smell; the more you can eliminate branching, the better. If you find yourself going beyond about 3 levels deep, stop and think about how to simplify.
Additionally, with a lot of branching and hand-typed code, it's very likely that a subtle bug or typo will go unnoticed or that a denomination will be left out.
Use data structures
Consider using dictionaries and lists in place of blocks like:
twocount=0
onecount=0
halfcount=0
pttwocount=0
ptonecount=0
which can be elegantly and extensibly represented as:
denominations = [200, 100, 50, 10, 5, 2, 1]
used = {x: 0 for x in denominations}
In terms of efficiency, you can use math to handle amounts for each denomination in one fell swoop. Divide the remaining amount by each available denomination in descending order to determine how many of each coin will be chosen and subtract accordingly. For each denomination, we can now write a simple loop and eliminate branching completely:
for val in denominations:
used[val] += amount // val
amount -= val * used[val]
and print or show a final result of used like:
278 => {200: 1, 100: 0, 50: 1, 10: 2, 5: 1, 2: 1, 1: 1}
The end result of this is that we've reduced 27 lines down to 5 while improving efficiency, maintainability and dynamism.
By the way, if the denominations were a different currency, it's not guaranteed that this greedy approach will work. For example, if our available denominations are 25, 20 and 1 cents and we want to make change for 63 cents, the optimal solution is 6 coins (3x 20 and 3x 1). But the greedy algorithm produces 15 (2x 25 and 13x 1). Once you're comfortable with the greedy approach, research and try solving the problem using a non-greedy approach.

Resources