Модуль времени Python – неточное добавление

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: не уверен, подходит ли заголовок, и это мой первый пост, поэтому я учусь, как организовать его

ИСТОРИЯ КОДА:

Я создаю фрактальное пламя (повторяющиеся изображения) с помощью Apophysis7x и открываю файл .flame (этот файл не визуализируется, он просто содержит информацию для открытия в другом редакторе) в Fractal Fr0st, где я могу, например, автоматизировать итерацию, которая будет постоянно вращаться. тем самым создавая анимацию. Fractal Fr0st использует python для такой автоматизации, однако она постоянна. Я создаю музыку и надеюсь когда-нибудь завершить мой код, чтобы чередовать вращения в зависимости от BPM моей песни. Я начал кодировать самостоятельно, чтобы создать последовательность тик-так, даже не отредактировав фактический файл Python, связанный с программой (этот код пока не совсем важен, но опубликован в конце)

ИНФОРМАЦИЯ ПО ПРОБЛЕМЕ:

Я с самого начала знаю, что Python не самый быстрый язык, но я не уверен на 100%, что мой код просто недостаточно эффективен, чтобы не отставать от того, что я пытаюсь получить на выходе. Пожалуйста, взгляните на мой код и комментарии, поскольку я пытаюсь максимально подробно объяснить, что происходит.

timer.py:

import time

#Feel free to test as you'd like
#Suppose we have a song at 140 Beat Per Minute (BPM) -> There is approximately 2.33 Beats Per Second (BPS)
#So it takes approximately 0.43 (s) per beat
#So  if freq = 0.43, leng = *length of song*
#This program should alternate printing "tick" "tock" (or some action that I hope to code in the future)
#The Tick Tock output should come out every (in this case) .43 seconds

class timer(object):
    "takes in a frequency and duration of loop in seconds"

    def __init__(self,freq,leng):
        "input a frequency and duration in seconds"
        #rounded to 2 decimal places

        self.freq= round (freq,2)
        self.leng= round (leng,2)

    def ticker (self):
        "Starts the time and loop to be broken when max time is reached"

        counter = 0 #recorded out of curiosity to check just how many loops were made overall
        ticker = 0 #alternates the "tick" "tock"
        initTime = time.time() #records the initial upon starting the loop
        rnded = 0 #initial rounded time set at 0 *see the first if statement below*


        while True:

            stop = time.time() #The time recorded during the loop *stopwatch so-to-speak*
            passed = stop - initTime #The time passed from initial launch to time during the loop

            if rnded == self.leng:
                "When the time that has passed reaches time limit print stats and break"

                return print("Loops:".ljust(10).rjust(14) + "Initial Time:".ljust(17).rjust(21) + "Time Stopped:".ljust(17).rjust(21)+ "Passed:".ljust(11).rjust(15) + "Total Ticks:".ljust(16).rjust(20) + "\n" + (str (counter) ).ljust(10).rjust(14) +(str (initTime) ).ljust(17).rjust(21)+ (str (stop) ).ljust(17).rjust(21)+(str (rnded) ).ljust(11).rjust(15) + (str (ticker) ).ljust(11).rjust(15) )

            elif round(passed,2) == rnded:
                "Prevents duplicates, ie: previous stopped at .9999 (1.0) and current loop sopped at 1.001 (1.0) skip" 
                #If your current value happened to be rounded to the previous value don't continue

                #uncomment bellow for debugging
                #print ("*TEST* \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")
                pass

            else:
                "Tick tock code"

                rnded = round(passed,2) #now set current time differnce value after being checked for duplicates
                if rnded % self.freq == 0 and rnded != 0:

                    "if the time passed mod the frequency is 0 continue to execute tick tock"

                    if ticker % 2 == 0:
                        #gives stats for debugging purposes, delete, after "tick" / "tock" if desired
                        #prints in the meantime in the future I would like an action to be done with music in another program as precise as possible

                        print ("Tick \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")

                    elif ticker % 2 == 1:
                        print ("Tock \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")

                    ticker +=1 #only increases if tick/tock was processed        

        counter += 1 #counts how many times loop was accessed, use this as you'd like

    def chngFreq(self, freq):
        'Changes current frequency'
        self.freq = round (freq,2)

    def chngLeng(self,leng):
        'Changes current duration'
        self.leng = round(leng,2)

Пример 1:

Вывод пропускает несколько «тиков» / «тиков» по ​​мере того, как частота становится быстрее (я просто тестировал, например, когда частота была 0,01 (с) и общее время 4 (с), всего должно быть 399 тиков. тем не менее, он достиг только 5 тиков.Моя теория заключается в том, что, поскольку для прохождения цикла требуется время (хотя и очень небольшое время), он может пропустить тик из-за некоторой вероятности того, что до перехода к следующему тику от . От 01 до .02 до округления это могло быть галочкой, но код обрабатывался недостаточно быстро Я округлил до конца, чтобы быть как можно более точным, и сделал часть «элиф», чтобы предотвратить дублирование (см. код выше)

Вывод:

>>>s = timer(.01,4)
>>>s.ticker()
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409793.6937597 
Time passed:  0.02   Tick Counter:  0 
---
Tock     Initiated:  1502409793.6775877      Time stopped:  1502409793.7562232 
Time passed:  0.08   Tick Counter:  1 
---
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409793.8409605 
Time passed:  0.16   Tick Counter:  2 
 ---
Tock     Initiated:  1502409793.6775877      Time stopped:  1502409793.9940765 
Time passed:  0.32   Tick Counter:  3 
---
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409794.9579115 
Time passed:  1.28   Tick Counter:  4 
---
Loops:        Initial Time:        Time Stopped:        Passed:        Total Ticks:    
5473896      1502409793.6775877    1502409797.680171    4.0            5

Обратите внимание, что иногда галочка засчитывается только для 2^x, например: .01, .02, .04, .08, .16, .32... < strong>Вместо: .01, .02, .03, .04, .05, .06...

Я знаю, что это не тот пример, который я привел в своем коде, но это верно всякий раз, когда я пробую более приблизительную частоту, такую ​​​​как 0,23 или 0,01, но чем больше частота, независимо от точности, например: 4,29, тем больше вероятность того, что она будет точной. вернуть "галочки" 4.29, 8.58, 12.87 и т.д...

Пример 2

В следующем примере вы можете сказать, что он работает отлично, так как ему дается больше времени для обработки между тиками.

>>>s = timer(1,4)
>>>s.ticker()
Tick     Initiated:  1502411198.8088021      Time stopped:  1502411199.8091898 
Time passed:  1.0    Tick Counter:  0 
---
Tock     Initiated:  1502411198.8088021      Time stopped:  1502411200.8089292 
Time passed:  2.0    Tick Counter:  1 
---
Tick     Initiated:  1502411198.8088021      Time stopped:  1502411201.81057 
Time passed:  3.0    Tick Counter:  2 
---
Tock     Initiated:  1502411198.8088021      Time stopped:  1502411202.8121786 
Time passed:  4.0    Tick Counter:  3 
---
Loops:        Initial Time:        Time Stopped:        Passed:        Total Ticks:    
5586557      1502411198.8088021   1502411202.8328226    4.0            4

Пожалуйста, дайте мне знать, если что-то в этом коде неясно, но обязательно сначала прочитайте каждый комментарий и запустите код или измените его, если это необходимо.

Как только моя предыдущая проблема будет решена, я надеюсь изменить раздел «if x.animate:» следующего кода, который используется в Fractal Fr0st.

default.py:

update_flame = False

while True:
    for x in flame.xform:
        if x.animate:
            x.rotate(-3)
    preview()

Наконец, спасибо заранее, я знаю, что это действительно долго, но я действительно пытаюсь понять, что происходит с python/как он работает, независимо от моей конечной цели.

Также, если какие-либо другие методы быстрее/эффективнее, сообщите мне. Спасибо!!!


person BHerna    schedule 11.08.2017    source источник
comment
Похоже, ваша основная проблема заключается в том, что вы сравниваете числа с плавающей запятой для точного равенства. Это простое совпадение в тех редких случаях, когда это происходит: числа с плавающей запятой просто не представляют собой какое-то конкретное число, это диапазоны. Многие из используемых вами чисел, например 0,01, не могут быть точно представлены как числа с плавающей запятой, и они будут отдаляться от правильного значения только по мере того, как вы выполняете с ними математические операции.   -  person jasonharper    schedule 11.08.2017
comment
Интересно, спасибо, что разъяснили. Я знаю разницу между float и int, но то, что вы только что упомянули, действительно заставило меня задуматься. Поскольку я также начинаю кодировать на Java, я теперь вижу акцент и использую двойное число, а не само число с плавающей запятой, хотя я знаю, что у python нет двойника, не могли бы вы также уточнить между ними? Наконец, как я могу решить эту проблему, сравнивая числа с плавающей запятой с точными значениями, такими как 0,01, и как именно программисты кодируют данные с точными значениями (0,01)? Еще раз спасибо!   -  person BHerna    schedule 11.08.2017
comment
Число с плавающей запятой в Python такое же, как и в Java: в каждом объекте Python достаточно накладных расходов, поэтому усилия по поддержке как 32-, так и 64-битных значений с плавающей запятой не стоят усилий. Меньший размер может быть уместным, если вы работаете с массивами из миллионов значений, и в этом случае вы захотите использовать модуль, такой как numpy, для эффективной работы с такими массивами.   -  person jasonharper    schedule 11.08.2017
comment
Итак, я провел небольшое исследование и наткнулся на numpy и scipy, поэтому я не знаю, что взять, но затем в одном из комментариев здесь на это был дан ответ: В последний раз, когда я проверял это, метод scipy __init__ выполняет from numpy import *, что означает, что он в основном влечет за собой все numpy ?   -  person BHerna    schedule 11.08.2017
comment
@jasonharper «поплавки просто не представляют какое-то конкретное число, это диапазоны» абсолютно неверно. Каждое конечное число с плавающей запятой действительно представляет определенное число, но число с плавающей запятой, полученное в результате вычисления с плавающей запятой, обычно отличается от действительного, что привело бы к применению тех же операций к вещественным числам.   -  person Pascal Cuoq    schedule 11.08.2017


Ответы (1)


После небольшого исследования и небольшого возни, я не только нашел какое-то решение (я нашел реальную проблему, и она все еще возникает), но также сделал мой код НАМНОГО КОРОЧЕ. Посмотри.

НОВЫЙ И УЛУЧШЕННЫЙ КОД:

import time

#Feel free to test as you'd like
#Suppose we have a song at 140 Beat Per Minute (BPM) -> There is approximately 2.33 Beats Per Second (BPS)
#So it takes approximately 0.43 (s) per beat
#So  if freq = 0.43, leng = *length of song*
#This program should alternate printing "tick" "tock" (or some action that I hope to code in the future)
#The Tick Tock output should come out every (in this case) .43 seconds

class timer2(object):
    "takes in a frequency and duration of loop in seconds"

    def __init__(self,freq,leng):
        "input a frequency and duration in seconds"
        #rounded to 2 decimal places

        self.freq= round (freq,2)
        self.leng= round (leng,2)

    def ticker (self):
        "Starts the time and loop to be broken by freq"

        self.ticker = 0 #alternates the "tick" "tock"
        self.passed = 0 

        while round (self.passed,2) != self.leng:

            time.sleep(self.freq)

            if self.ticker % 2 == 0:
            #prints in the meantime in the future I would like an action to be done with music in another program as precise as possible

                print ("***TICK*** \t Tick Counter: ", self.ticker)

            elif self.ticker % 2 == 1:

                print ("***TOCK*** \t Tick Counter: ", self.ticker)

            self.ticker +=1 #only increases if tick/tock was processed

            self.passed += self.freq
            round (self.passed,2)

            print ("Time passed: ", self.passed, "(s)","\n ---")

        print ("Ticks: \t Time Passed: \n {} \t {}(s)".format(self.ticker,self.passed))

    def chngFreq(self, freq):
        'Changes current frequency'
        self.freq = round (freq,2)

    def chngLeng(self,leng):
        'Changes current duration'
        self.leng = round(leng,2)

Обратите внимание на множество методов round(), которые я объясню чуть позже.

Мой основной вклад:

>>>s = timer2(1,4)
>>>s.ticker()

Это работает, как и ожидалось: 1,2,3,4. Однако, возвращаясь к тому, что я хотел изначально, если моя частота мала, например, 0,43, это точно не сработает. Я включаю первый и последний 4 выхода для равномерной частоты 0,01.

ВЫВОД:

ПЕРВЫЕ ЧЕТЫРЕ:

>>>s = timer2(.01,4)
>>>s.ticker()
***TICK***   Tick Counter:  0
Time passed:  0.01 (s) 
---
***TOCK***   Tick Counter:  1
Time passed:  0.02 (s) 
---
***TICK***   Tick Counter:  2
Time passed:  0.03 (s) 
---
***TOCK***   Tick Counter:  3
Time passed:  0.04 (s) 
---

ПОСЛЕДНИЕ ЧЕТЫРЕ:

***TICK***   Tick Counter:  396
Times passed:  3.9699999999999593 (s) 
---
***TOCK***   Tick Counter:  397
Times passed:  3.979999999999959 (s) 
---
***TICK***   Tick Counter:  398
Times passed:  3.989999999999959 (s) 
---
***TOCK***   Tick Counter:  399
Times passed:  3.9999999999999587 (s) 
---
Ticks:   Time Passed: 
 400     3.9999999999999587(s)

Обратите внимание, что в самом конце прошедшее время должно быть равно 4 (с), точно так же, как если частота равна 1. И почему прошедшее время действительно уродливое с плавающей запятой? Это было нормально для первых четырех, после чего он дает длинные десятичные дроби. Я открою еще один поток, дающий простой цикл (я все еще хотел бы, чтобы это было пересмотрено).

Python добавляет неправильно, и это очень раздражает... .31 + .01 должно быть: .32 и далее, .34, .35, .36, .37 и т. д. Даже после округления времени до 2 знаков после запятой его не работает. т.е. скажем, даже если я не могу исправить уродливые поплавки (.310000015684668), округление должно быть (.31), но Python этого не выполнит.

***ЕСЛИ Я ИСКЛЮЧАЮ ВСЕ ОКРУГЛЕНИЯ, Я ВСЁ ПОЛУЧУ ДЛИННЫЕ ПЛАВАЮЩИЕ, ПОЭТОМУ ЭТО НЕ МЕТОД ОКРУГЛЕНИЯ () ДЛЯ НАЧАЛА***

person BHerna    schedule 16.08.2017