0

The values in the a list are the math division values that come from the API.

To convert a string into a calculation, I use eval().

The values in correct_list are the final results of this division add +1.
The API indicates this are the exact results of this calculation to work with its data.

But I'm having difficulties when these divisions have several decimal, each model I tried to format so that the limit of decimals was 2, the created list doesn't match perfectly with the correct result:

import math
import decimal
decimal.getcontext().rounding = decimal.ROUND_CEILING

def main():
    a = ['11/10', '29/20', '5/4', '1/2', '10/11', '2/1', '4/7', '11/8', '13/10', '1/8', '4/11', '5/4', '163/100', '11/20', '17/20', '23/20', '6/4', '1/1', '8/5']
    b = []
    correct_list = ['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.57', '2.38', '2.30', '1.13', '1.36', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
    for c in a:
        b.append(f'{round(eval(c)+1, ndigits=2)}')
    print(b)
    if b == correct_list:
        print('Ok. Amazing Match!')

if __name__ == '__main__':
    main()

These were all my attempts:

b.append(f'{eval(c)+1:.2f}')
['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.57', '2.38', '2.30', '1.12', '1.36', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
b.append(f'{math.ceil((eval(c)+1) * 100.0) / 100.0:.2f}')
['2.10', '2.46', '2.25', '1.50', '1.91', '3.00', '1.58', '2.38', '2.30', '1.13', '1.37', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
b.append(f'{float(round(decimal.Decimal(str(eval(c)+1)), ndigits=2)):.2f}')
['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.58', '2.38', '2.30', '1.13', '1.37', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
b.append(f'{round(eval(c)+1, ndigits=2):.2f}')
['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.57', '2.38', '2.30', '1.12', '1.36', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']

None delivered the results that perfectly match the API calculation model.

Digital Farmer
  • 1,705
  • 5
  • 17
  • 67
  • 1
    To properly use `Decimal`, you'd have to parse the string into numerator/denominator values, and pass those as *separate parameters* to `Decimal()`. Calling `eval()` on the string produces an inherently imprecise float result; passing that to `Decimal()` cannot fix the damage. – jasonharper May 04 '22 at 17:02
  • 1
    Related: https://stackoverflow.com/questions/588004/is-floating-point-math-broken. In general, we cannot rely on the exact value of floating point arithmetic for anything. Rounding should only be for display purposes in any app. It's better to use `f''` string formatting for this purpose than `round()`, `ceil()`, or `floor()`. I understand this is probably an exercise. I still suggest trying formatting directly rather than doing any calculations. – Code-Apprentice May 04 '22 at 17:04
  • 1
    Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Code-Apprentice May 04 '22 at 17:05
  • 1
    Hello @Code-Apprentice , Samwise was able to get the point of interest in my need, which was in relation to fractional odds being converted into decimal odds, anyway, thanks for the comments and link, very useful! – Digital Farmer May 04 '22 at 17:17
  • 1
    @BrondbyIF Glad to hear you found a solution. When working with floating point numbers, it is good to understand their limitations. Otherwise, you will be quickly confused by some of the behavior. – Code-Apprentice May 04 '22 at 17:52

1 Answers1

1

The only case that the normal round() function doesn't handle the way you want is that it rounds 0.005 down to 0.00 instead of up to 0.01. So if you're willing to be cheesy, just add 1.001 instead of 1, and then use normal rounding.

a = ['11/10', '29/20', '5/4', '1/2', '10/11', '2/1', '4/7', '11/8', '13/10', '1/8', '4/11', '5/4', '163/100', '11/20', '17/20', '23/20', '6/4', '1/1', '8/5']
correct_list = ['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.57', '2.38', '2.30', '1.13', '1.36', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
b = [f'{float.__truediv__(*map(float, c.split("/"))) + 1.001:.2f}' for c in a]
if b == correct_list:
    print('Ok. Amazing Match!')  # succeeds

If you want to use the decimal module to do it in a non-cheesy way, you need to do the math with Decimal objects, and the rounding method you want is ROUND_HALF_UP.

import decimal
decimal.getcontext().rounding = decimal.ROUND_HALF_UP

a = ['11/10', '29/20', '5/4', '1/2', '10/11', '2/1', '4/7', '11/8', '13/10', '1/8', '4/11', '5/4', '163/100', '11/20', '17/20', '23/20', '6/4', '1/1', '8/5']
correct_list = ['2.10', '2.45', '2.25', '1.50', '1.91', '3.00', '1.57', '2.38', '2.30', '1.13', '1.36', '2.25', '2.63', '1.55', '1.85', '2.15', '2.50', '2.00', '2.60']
b = []
for c in a:
    n, d = map(int, c.split("/"))
    result = decimal.Decimal(1) + decimal.Decimal(n) / decimal.Decimal(d)
    b.append(f'{result:.2f}')
if b == correct_list:
    print('Ok. Amazing Match!')  # succeeds

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • Hi @Samwise An additional question please, can we say that this method is the most useful to use in converting fractional odds into decimal odds? I just tested here on two API's and they matched perfectly in both. – Digital Farmer May 04 '22 at 17:04
  • 1
    No, it's a complete hack, which IMO is all that an API that depends on precise floating point numbers deserves (any API that requires exact precision should use ints; anything involving floats should be expected to be imprecise). :) I'll add a section about how to do it "properly" with the `decimal` module, which is harder. – Samwise May 04 '22 at 17:08
  • Ah I understand now, Haha. Alright, anyway it worked perfectly, but the additional learning will be very welcome too! – Digital Farmer May 04 '22 at 17:12