На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! правила раздела Алгоритмы
1. Помните, что название темы должно хоть как-то отражать ее содержимое (не создавайте темы с заголовком ПОМОГИТЕ, HELP и т.д.). Злоупотребление заглавными буквами в заголовках тем ЗАПРЕЩЕНО.
2. При создании темы постарайтесь, как можно более точно описать проблему, а не ограничиваться общими понятиями и определениями.
3. Приводимые фрагменты исходного кода старайтесь выделять тегами code.../code
4. Помните, чем подробнее Вы опишете свою проблему, тем быстрее получите вразумительный совет
5. Запрещено поднимать неактуальные темы (ПРИМЕР: запрещено отвечать на вопрос из серии "срочно надо", заданный в 2003 году)
6. И не забывайте о кнопочках TRANSLIT и РУССКАЯ КЛАВИАТУРА, если не можете писать в русской раскладке :)
Модераторы: Akina, shadeofgray
Страницы: (3) 1 [2] 3  все  ( Перейти к последнему сообщению )  
> Приближённое сокращение дроби.
    Цитата JoeUser @
    Че скажите, или опять шило?

    Шило. Тут уже два простых метода описали, дающие 100% точность. Зачем ещё один, да ещё сложный и неточный?
      Цитата OpenGL @
      Тут уже два простых метода описали, дающие 100% точность.

      Хотелось бы лицезреть "простоту" и "100% точность" на каком-нить примере, хотя бы 17/33.
        Да на самом деле задача всё равно переборная получается.

        Имеется M/N. Допустим, M<N. Необходимо найти ближайшую дробь, знаменатель которой не превышает K. Решение - для всех X от K до 2 рассчитать ближайшую дробь (числитель = INT(M*X/N), отклонение = M/N - INT(M*X/N)/X, где INT - математическое округление), и выбрать X, для которого ABS(отклонение) минимален.

        Единственная зримая оптимизация - это при переборе (в сторону уменьшения) можно не проверять значения X, являющиеся делителями ранее проверенных.

        Проблема метода - в точности вычислений.
        Сообщение отредактировано: Akina -
          Цитата OpenGL @
          Например, для 2/7 и ограничения 6 ответ 1/4

          Нашел калькулятор логарифмов с нормальной точностью:

          2/7 = 2^log2(2)/2^log2(7) = 2^1/2^2.8073549221 = 1/3.5000000001

          Получите: 1/4 :whistle:
            Цитата JoeUser @
            Хотелось бы лицезреть "простоту" и "100% точность" на каком-нить примере, хотя бы 17/33.

            А зачем? Видно, что это наколеночный велосипед, мало отличающийся от велосипеда Pavlovsky - то же самое деление числителя и знаменателя на одно и то же число. При наличии точных методов смысла в нём нет.
              Простой брутфорс:
              ExpandedWrap disabled
                num=17 'числитель
                den=33 'знаменатель
                max=8  'предел
                 
                div=num/den
                er=max
                nn=0
                dd=0
                for n=1 to max
                  d=clng(n/div)
                  if d<=max then
                    if er>abs(n/d-div) then
                      er=abs(n/d-div)
                      nn=n
                      dd=d
                    end if
                  end if
                next
                 
                msgbox cstr(nn) & "/" & cstr(dd)

              Это VBScript, просто сохраните это в текстовый файл с расширением .vbs и запускайте.
              Сообщение отредактировано: Mikle -
                Спасибо за цепные дроби, ещё не успел почитать, но уже вижу что в эту сторону стоит покопать.
                По поводу разрядностей и величин, всё очень просто, я сделал библиотеку для работы с дробями (из спортивного интереса) и, естественно в любых вычислениях без квадратного корня - никуда. Но оказалось, что дроби настолько быстро растут, что 64 битные значения перекрываются на раз.
                Перекрывается не просто дробь, а в момент сложения там получается числитель*знаменатель + числитель*знаменатель и результат очень большой и никуда уже не лезет. При этом, метод Ньютона может отталкиваться от любых значений и будет двигаться в правильном направлении, но очень трудно бороться с переполнениями.

                Например, корень из 99999:
                1250049999/50000 = 25000,99998
                646907007/51742 = 12502,5512543002
                902980466/144355 = 6255,27668594784--где-то тут уже начались приближённые дроби
                1181194331/376700 = 3135,63666312716
                2133521117/1347120 = 1583,76471064196
                1783350728/2165699 = 823,452718036994
                311071000/658427 = 472,44569253691
                16599542/48529 = 342,054070761813
                1641681305/5175518 = 317,201351632822
                1401161871/4430864 = 316,227686293238
                316/1 = 316 -- Тут отдельне условие сработало, для точного извлечения целых корней.
                199855/632 = 316,226265822785
                998550270/3157709 = 316,226184870107
                58698221/185621 = 316,226186692238
                657173372/2078175 = 316,226194617874
                156455779/494759 = 316,226241463015
                778147583/2460731 = 316,226187665373
                364559862/1152845 = 316,226259384393

                Восемь девяток 99999999

                1589457257/63 = 25229480,2698413
                418287974/33 = 12675393,1515152
                1738120497/274 = 6343505,46350365--Тут пошли приближения
                333462641/105 = 3175834,67619048
                1683287173/1060 = 1588006,76698113
                1513894167/1906 = 794278,156873033
                564460402/1421 = 397227,587614356
                1452190873/7307 = 198739,684275352
                700088857/7027 = 99628,4128362032
                953794307/18956 = 50316,2221460224
                1043806002/39913 = 26152,0307168091
                1550758537/103467 = 14987,9530381668
                1285396173/118688 = 10830,0432478431
                723990051/72169 = 10031,8703459934
                865304829/86530 = 10000,0558072345
                1763116023/176311 = 10000,0341612265
                1444717817/144471 = 10000,0541077448
                647951621/64795 = 10000,0250173625
                1722111243/172211 = 10000,0072178897
                1478838442/147883 = 10000,0570856691
                1810450224/181045 = 10000,0012372615
                173690046/17369 = 10000,0026483966
                327870773/32787 = 10000,0235764175
                1518800379/151880 = 10000,0024953911
                1697447567/169744 = 10000,0445788953
                357791208/35779 = 10000,0337628218
                2067026865/206702 = 10000,0332120637
                1676812917/167681 = 10000,0173961272
                193970116/19397 = 10000,0059803062
                1288670094/128867 = 10000,0007294342
                733212401/73321 = 10000,0327464164
                731114490/73111 = 10000,0614134672
                1665891734/166589 = 10000,0104088505
                112930094/11293 = 10000,0083237404
                1430869994/143087 = 9999,99995806747
                9999/1 = 9999
                10000/1 = 10000
                199999999/20000 = 9999,99995
                10000/1 = 10000
                199999999/20000 = 9999,99995
                10000/1 = 10000
                --Тут метод зацикливается,потому что не может два раза подряд получить правильный корень 9999,99995

                Корень из 2
                1/1 = 1
                3/2 = 1,5
                17/12 = 1,41666666666667
                577/408 = 1,41421568627451
                665857/470832 = 1,41421356237469
                941664/665857 = 1,4142135623715
                941664/665857 = 1,4142135623715
                Как видно мы даже не достигли точности виндового калькулятора.

                А вот что будет, если не бороться с переполнением:
                1/1 = 1
                3/2 = 1,5
                17/12 = 1,41666666666667
                577/408 = 1,41421568627451
                665857/470832 = 1,41421356237469
                886731088897/627013566048 = 1,4142135623731
                575684128022077/2171326443582699 = 0,265130160286813--Это уже всё, того, дальше портянку не показываю

                Видно, что уже третья итерация уходит в переполнение, а погрешности приведённых дробей столь высоки что не смотря на всю мощь сверхсходимого метода касательных, точность в дробной части вообще не растёт.
                На всякий случай формула x = (x_old + x_start/x_old)/2
                  Цитата KIA @
                  Например, корень из 99999:
                  1250049999/50000 = 25000,99998
                  646907007/51742 = 12502,5512543002
                  902980466/144355 = 6255,27668594784--где-то тут уже начались приближённые дроби
                  1181194331/376700 = 3135,63666312716
                  2133521117/1347120 = 1583,76471064196
                  1783350728/2165699 = 823,452718036994
                  311071000/658427 = 472,44569253691
                  16599542/48529 = 342,054070761813
                  1641681305/5175518 = 317,201351632822
                  1401161871/4430864 = 316,227686293238
                  316/1 = 316 -- Тут отдельне условие сработало, для точного извлечения целых корней.
                  199855/632 = 316,226265822785
                  998550270/3157709 = 316,226184870107
                  58698221/185621 = 316,226186692238
                  657173372/2078175 = 316,226194617874
                  156455779/494759 = 316,226241463015
                  778147583/2460731 = 316,226187665373
                  364559862/1152845 = 316,226259384393

                  Виндовый калькулятор считает, что sqr(99999) = 316,22618487405498217065397744334.
                  Простейший код даёт 303068962 / 958393 = 316,22618487405479797953449159165 - т.е. различие в 13-м знаке после запятой. И это я работал в вульгарных Double и знаменатель не более 1 000 000.
                  Скрытый текст
                  ExpandedWrap disabled
                    Dim num As Double
                    Dim sqrt As Double
                    Dim i As Double
                    Dim j As Double
                    Dim delta As Double
                    Dim curdelta As Double
                    num = Val(Text1.Text) ' Число для извлечения кв. корня
                    sqrt = Math.sqr(num)
                    Text2.Text = sqrt ' Расчётный корень в точности Double
                    curdelta = 1000
                    For i = 2 To 1000000
                        j = Math.Round(i * sqrt)
                        delta = Math.Abs(sqrt - j / i)
                        If delta < curdelta Then
                            curdelta = delta
                            Text4.Text = j ' Числитель
                            Text5.Text = i ' Знаменатель
                            Text3.Text = j / i ' Значение дроби
                        End If
                    Next i


                  Если увеличить диапазон ещё на порядок, то результат 1714254875 / 5420977 = 316,22618487405499045651733995551 - т.е. различие в 14 знаке.

                  Добавлено
                  Хотя я не понимаю, как извлечение корней соотносится с "псевдо-сокращением".
                  Сообщение отредактировано: Akina -
                    Цитата Akina @

                    Простейший код даёт 303068962 / 958393 = 316,22618487405479797953449159165 - т.е. различие в 13-м знаке после запятой. И это я работал в вульгарных Double и знаменатель не более 1 000 000.

                    Но я-то ничего не выдумываю, вычисления по Ньютону дают именно такие большущие несократимые дроби и именно числа с плавающей точкой тут и планируется заменить. Можно на пальцах проверить на корне из двух (вот ссылка на описание, если кому-то мало формулы http://mech.math.msu.su/~shvetz/54/inf/per...ot_sIdeas.xhtml):
                    M
                    Ссылочку поправил

                    x_start = 2
                    x_old = 17/12

                    x = (17/12 + 2/17/12)/2=(17*17/12*17+24*12/17*12)/2=(289/204+288/204)/2=577/408 выше в выкладках именно такой результат.

                    давайте для x_old = 886731088897/627013566048

                    x = (886731088897/627013566048 + 2/886731088897/627013566048)/2=(786292024016459316676609/555992422174934068969056 + 786292024016459316676608/555992422174934068969056)/2=1572584048032918633353217/1 111 984 844 349 868 137 938 112 второе число уже в int64 не лезет, вручную сокращение делать не буду. = 1,4142135623730950488016887242097, что похоже на правду (инженерный калькулятор даёт в точности такое же значение, то есть метод Ньотона работает очень хорошо сам по себе).
                    Формула для вычисления корня тут фигурирует, на самом деле, только для иллюстрации. В формуле вычисления ничего сверхсложного нет, она - элементарна, но порождает очень большие числа в числителях и знаменателях. Если использовать дроби в практических целях, то это же будет на каждом шагу. Получается, что вести точные расчёты дробями (плавающая точка это всегда потеря точности), не получается.
                    Отсюда и возник вопрос - хорошо, не получается сохранить точность, двайте получим какое-нибудь приближение, причём такое, что бы ни числитель ни знаменатель не были больше чем сколько-то разрядов, что бы с этими дробями можно было произвести ещё какие-нибудь вычисления и результат бы поместился в int64 (9 223 372 036 854 775 807). Инт это 18-19 разрядов, у нас же уже на представленной итерации 25.
                    Ну а уже когда получим приближение, посмотреть насколько страдает точность и есть ли какие-нибудь хотя бы гипотетические преимущества перед плавающей точкой. При этом исследуя проблему, я наталкнулся на мнение, что такие большие несократимые дроби в вычислениях, вообще говоря - не характерны, а получаются именно при попытке приближения к иррациональным числам.
                    Сообщение отредактировано: JoeUser -
                      Цитата KIA @
                      вести точные расчёты дробями (плавающая точка это всегда потеря точности), не получается.
                      Отсюда и возник вопрос - хорошо, не получается сохранить точность, двайте получим какое-нибудь приближение, причём такое, что бы ни числитель ни знаменатель не были больше чем сколько-то разрядов, что бы с этими дробями можно было произвести ещё какие-нибудь вычисления и результат бы поместился в int64

                      Ограничивая разрядность операнда, мы точно так же ограничиваем и точность. Так что если нужны точные расчёты, надо не в дроби лезть, а в "сверхдлинную" математику. Которая, кстати, в системе уже имеется - см. напр. Длинная арифметика от Microsoft.
                        Длинная арифметика никак не поможет описать бесконечные дроби, и (1/3)*3 не будет равно еденице. Собственно полно примеров подобных казусов на обычных числах с плавающей точкой, так же всевозможные итеративные функции накапливают ошибки вподь до того, что сходящийся ряд превращается в расходящийся, у дробей таких проблем нет, во многих случаях вычисления будут происходить абсолютно точно. Дроби это расширение чисел до рациональных, к сожалению ещё остаётся класс иррациональных чисел и их реализация на компьютере становится уж очень не удобной и медленной, из-за того, что там, фактически, придётся держать список многочленов, в дробях же всегда достаточно только числителя и знаменателя. Разрядность мы ограничиваем не конечной величины, а составных частей дроби и имея знаменатель в 18 разрядов, можно достичь точости в 18 знаков после запятой, как видно из примеров, проблема как раз в том, что в вычислениях очень резко растёт точность и получается, что после 13 знаков получаем 25, вот эти 25 можно огрубить до 18 и на этом остановиться.

                        Кстати, в вашем же примере: 303068962 / 958393, знаменатель всего 6 знаков а точность 13, так что хорошая технология для приведения дроби может дать весьма хороший результат.
                        Сообщение отредактировано: KIA -
                          Длинная математика поможет работать с дробями без ограничения значений числителя и знаменателя - т.е. с любой (в разумных пределах) требуемой точностью.
                            Цитата Pacific @
                            KIA
                            Цепные дроби решают твою задачу оптимальным образом:
                            Не так. При заданных ограничениях на числитель и знаменатель, довольно часто можно подобрать приближение лучше
                              Цитата KIA @
                              Кстати, в вашем же примере: 303068962 / 958393, знаменатель всего 6 знаков а точность 13, так что хорошая технология для приведения дроби может дать весьма хороший результат.


                              Akina прав.

                              Цитата Akina @
                              Длинная математика поможет работать с дробями без ограничения значений числителя и знаменателя - т.е. с любой (в разумных пределах) требуемой точностью.


                              При сознательном занижении точности - каждая последующая операция будет только "накапливать" неточность, причем накопление пойдет в сторону уменьшения количества правильных значащих цифр. ИМХО, это тупиковый вариант. Длинная математика это решит.

                              Как вариант, вижу - представление чисел в виде динамических битовых массивов и выполнение операций над ними. Для ускорения операций умножения/деления рекомендую прочесть дисциплину "Прикладная теория цифровых автоматов" (ссылка навскидку). Типа умножение на два разряда одновременно, матричное умножение ... В свое время изучал этот мозговынос, в диплом оценка не попала (сдал экзамен, но перевелся), однако содержание курса помню.
                                Точный алгоритм что-то не увидел.

                                Пусть наша дробь x (неважно, чем она является дробью или вещественным числом) лежит в интервале между соседними целыми числами n1 и n2
                                d1 = d2 = 1.
                                Имеем n1/d1 < x < n2/d2

                                А далее в цикле
                                Формируем новое приближение (n1+n2)/(d1+d2) Это число удовлетворяет неравенству n1/d1 < (n1+n2)/(d1+d2) < n2/d2 и имеет наименьшие числитель и знаменатель среди всех рациональных чисел, лежащих в этом промежутке
                                Если эта дробь не удовлетворяет ограничениям, значит в качестве результата приближения выбираем ту границу, которая ближе к приближаемому числу.
                                Иначе сдвигаем одну из старых границ и формируем следующее приближение.

                                Часто это приближение совпадает с приближением, полученным усечением цепной дроби.
                                Кстати, ничего к исходной дроби прибавлять не нужно, пусть цепная в какой-то момент заканчивается.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (3) 1 [2] 3  все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0530 ]   [ 15 queries used ]   [ Generated: 17.11.25, 09:39 GMT ]