Оптимизированный алгоритм преобразования десятичной дроби в красивую дробь

Вместо того, чтобы преобразовывать произвольное десятичное число в точную дробь (что-то вроде 323527/4362363), я пытаюсь преобразовать просто в обычные легко различимые (с точки зрения удобочитаемости) величины, такие как 1/2, 1/4, 1/8. и т.п.

Помимо использования серии сравнений «если-то», «меньше/равно» и т. д., существуют ли для этого более оптимизированные методы?

Изменить: в моем конкретном случае допустимы приближения. Идея состоит в том, что 0,251243 ~ 0,25 = 1/4 - в моем случае использования это «достаточно хорошо», причем последнее более предпочтительно для удобочитаемости с точки зрения быстрого индикатора (не используется для расчетов, просто используется для отображения числовых значений).


person ina    schedule 13.01.2011    source источник
comment
Ваш вопрос расплывчатый. Как вы определяете удобочитаемость для человека? И что еще более важно, что, если нет удобочитаемой эквивалентной формы? Вы допускаете приближения? Лично я не вижу простого способа написать ваш пример 323526/4362363 в удобочитаемой форме, не прибегая к аппроксимации.   -  person Ehsan Foroughi    schedule 13.01.2011
comment
приближения приемлемы - 4-значной десятичной точности более чем достаточно   -  person ina    schedule 13.01.2011
comment
однако есть что-то еще для человека, а именно длина любого числа. Обычно дроби в форме 1/x просты, но 4/85 просто странно и лучше выражать их как 1/41. Конечно, семейство 1/x хорошо работает только для чисел меньше 0,5, а приближение к 0,4 с использованием означает большие потери... возможно, процентное представление было бы адекватным?   -  person Matthieu M.    schedule 13.01.2011
comment
(целое число) + x/y, где x и y меньше 10   -  person ina    schedule 13.01.2011


Ответы (5)


Найдите «приближение непрерывной дроби». В Википедии есть основное введение в статье «продолжение дроби», но есть оптимизированные алгоритмы, которые генерируют приближенное значение при генерации дроби.

Затем выберите некоторую эвристику остановки, комбинацию размера знаменателя и близости приближения, когда вы «достаточно близки».

person Anonymous    schedule 13.01.2011

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

person Nylon Smile    schedule 13.01.2011
comment
Я бы также рекомендовал ограничить ваш знаменатель чем-то разумным, чтобы избежать таких случаев, как 107842/1454121 (это ваш пример, уменьшенный). - person coreyward; 13.01.2011
comment
mea culpa - опечатка в начальной дроби... означало 7 вместо 6 ;-) - person ina; 13.01.2011
comment
Почему люди голосуют за это? Это вообще не отвечает на вопрос. Он спрашивает о приближении десятичных дробей к простым дробям, а не о сокращении дробей. - person Keith Irwin; 16.01.2011
comment
Вы правы, после редактирования вопроса мой ответ не имеет смысла. - person Nylon Smile; 16.01.2011

В дальнейшем я собираюсь предположить, что наши десятичные дроби попадают между 0 и 1. Должно быть просто адаптировать это к большим числам и отрицательным числам.

Вероятно, проще всего будет выбрать наибольший знаменатель, который вы считаете приемлемым, а затем создать список дробей от 0 до 1, знаменатель которых меньше или равен им. Обязательно избегайте дробей, которые можно упростить. Очевидно, что после того, как вы перечислили 1/2, вам не нужны 2/4. Вы можете избежать дробей, которые можно упростить, проверив, что НОД числителя и знаменателя равен 1 в соответствии с алгоритмом Евклида. Как только у вас будет список. Оцените их как числа с плавающей запятой (вероятно, удвоится, но тип данных, очевидно, зависит от вашего выбора языка программирования). Затем вставьте их в сбалансированное двоичное дерево поиска, хранящее как исходную дробь, так и оценку дроби с плавающей запятой. Вам нужно сделать это только один раз, чтобы настроить все изначально, поэтому время n * log (n) (где n - количество дробей) не очень много.

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

person Keith Irwin    schedule 13.01.2011

Вот предложение: предположим, что ваша начальная фракция равна p/q.

  1. Вычислить r = p/q как рациональное значение (с плавающей запятой) (например, r = float(p)/float(q))

  2. Вычислить округленное десятичное число x = int(10000*r)

  3. Вычислить НОД (наибольший общий знаменатель) x и 10000: s = НОД(x, 10000)

  4. Представьте результат как m/n, где m = x/s и n = y/s (ваш пример вычисляет до 371/5000)

Обычно все знаменатели числа 1000 вполне удобочитаемы.

Это может не дать наилучшего результата, если значение ближе к более простым случаям, таким как 1/3. Тем не менее, я лично нахожу 379/1000 гораздо более удобочитаемым для человека, чем 47/62 (что является самым коротким дробным представлением). Вы можете добавить несколько исключений для точной настройки такого процесса (например, вычисление p/GCD(p,q) , q/GCD(p,q) и принятие его, если одно из них является однозначным значением, прежде чем переходить к этому методу)

person Ehsan Foroughi    schedule 13.01.2011

Довольно глупое решение, только для «предварительного просмотра» фракции:

factor = 1/decimal
result = 1/Round(factor)
mult = 1

while (result = 1) {
  mult = mult * 10
  result = (1 * mult)/(Round(mult * factor))
}

result = simplify_with_GCD(result)

удачи!

person Agnius Vasiliauskas    schedule 14.01.2011