Любой, кто занимался разработкой биллинга или просто хранил денежную сумму в памяти знает, что её нужно хранить в целочисленном формате, а не вещественном, но при работе с другими объектами об этом вспоминают не всегда.
Но, конечно, в первую очередь этот пост для новичков. Думаю это не плохой пример для тех, кто утверждает «зачем мне знать как работают запросы/устроенны потоки/выполняются вычисления/…, если все это делает за меня компилятор и ОС!»
Итак, рассмотрим следующую задачу. Требуется написать простой скрипт, который вычисляет победителей соревнований из сходя из их результатов в двух попытках. Спортсмены, набравшие одинаковое количество баллов занимают одну позицию. Спортсмен, набравший меньше баллов, занимает следующую строчку в рейтинге. Попытки не равны по сложности и общий результат вычисляется из сходя из заданных коэффициентов.
Пример реализован на Python
class Member: def __init__(self, name="", first_result=0, second_result=0): self.name = name self.first_result = first_result self.second_result = second_result self.final_result = 0 self.position = 0 # Вывод информации об участнике def __repr__(self): return "\n" + str(self.position) + " "*10 + str(self.final_result) + " "*10 + self.name def proccess_raiting(members, first_koeff, second_koeff): #Подсчет результатов for member in members: member.final_result = member.first_result*first_koeff + member.second_result*second_koeff #Сортируем по итоговому результату members.sort(key=lambda x: x.final_result) #Расставляем места k = 0 prev_member = None for member in members: if prev_member: #Если результаты не равны, то следующий участник занимает на 1 позицию ниже, чем предыдущий if prev_member.final_result != member.final_result: k += 1 else: k +=1 member.position = k prev_member = member return members #Проверим работу скрипта: members = [Member("Ivan", 101.1, 50.2), Member("Alexander", 101.1, 58.2), Member("Vladimir", 101.5, 49.6)] print proccess_raiting(members, 3, 2)
И результат будет НЕ правильный:
>>>[ 1 403.7 Ivan, 2 403.7 Vladimir, 3 419.7 Alexander]
Посмотрим, почему так произошло, вычислив final_result отдельно:
>>> 101.1*3 + 50.2*2 403.69999999999993 >>> 101.5*3 + 49.6*2 403.7
Очевидно, что
>>>101.1*3 + 50.2*2==101.5*3 + 49.6*2 False
Проблема возникает из-за того, что некоторые десятичные числа в двоичной системе имеют вид бесконечной(или очень длинной) двоичной дроби. Поэтому такие числа не могут быть точно представлены в вещественных переменных
То, что эта ошибка проявляется не для всех вещественных чисел только усложняет процесс её поиска и уменьшает вероятность её обнаружить при тестировании.
Возможные решения на языке Python:
Вариант 1.
Использование типа Decimal
Member("Ivan", Decimal('101.1'), Decimal('50.2'))
Вариант 2.
Округление результатов:
member.final_result = round(member.first_result*first_koeff + member.second_result*second_koeff, 5)
P.S. Подробнее о представлении вещественных чисел в памяти и проблемах связанных с этим, можно прочитать здесь habrahabr.ru/post/112953/
ссылка на оригинал статьи http://habrahabr.ru/post/213067/
Добавить комментарий