OpenEdge abl truncate(log(4) / log(2)) должно быть 2 возвращает 1

У меня проблема с ошибкой округления с плавающей запятой в OpenEdge ABL / Progress 4GL.

display  truncate(log(4) / log(2) , 0) .

Это возвращает 1.0, но должно дать мне 2.0

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

display  truncate(log(4) / log(2)  + 0.00000001, 0) .

Что мне нужно, так это

find the largest x where 

p^x < n, p is prime, n and x is natural numbers.

=>

x = log(n) / log(p)

Любые взятия на этом?


person Zesar    schedule 02.10.2013    source источник


Ответы (4)


Ни одна числовая арифметическая система не является точной. Натуральные логарифмы 4 и 2 не могут быть представлены точно. Поскольку функция log может возвращать только представляемое значение, она возвращает приближение к точному математическому результату.

Иногда это приближение будет несколько выше математического результата. Иногда она будет немного ниже. Поэтому обычно нельзя ожидать, что log(x*x) будет ровно в два раза больше log(x).

В идеале высококачественная реализация log должна возвращать представимое значение, наиболее близкое к точному математическому значению. (Это называется «правильно округленный» результат.) В этом случае, и если вы используете двоичные числа с плавающей запятой (что является распространенным явлением), то log(4) всегда будет ровно вдвое больше log(2). Поскольку у вас этого не происходит, кажется, что используемая вами реализация log не обеспечивает правильно округленные результаты.

Однако для этой задачи вам также нужно, чтобы log(8) было ровно в три раза больше log(2), и так далее для получения дополнительных возможностей. Даже если реализация log вернет правильно округленные результаты, это не обязательно будет верно для всех необходимых вам значений. Для некоторого y = x5 log(y) может не быть ровно в пять раз больше log(x), потому что округление log(y) до ближайшего представимого значения может округляться в меньшую сторону, тогда как округление log(x) округляется в большую сторону, просто из-за того, где точные значения лежат относительно ближайших представимых значений.

Следовательно, вы не можете полагаться даже на наилучшую из возможных реализаций log, чтобы точно сказать, на сколько степеней x делится некоторое число y. Вы можете приблизиться, а затем проверить результат, подтвердив или опровергнув его с помощью целочисленной арифметики. Вероятно, существуют и другие подходы в зависимости от потребностей, специфичных для вашей ситуации.

person Eric Postpischil    schedule 02.10.2013
comment
Я немного смущен этим. С правильно округленным log и x, где x*x точно представимо, и не происходит никаких махинаций с переполнением или недостатком, конечно же, log(x*x) == 2*log(x)? - person tmyklebu; 02.10.2013
comment
@tmyklebu: Да, это было бы верно с двоичными числами с плавающей запятой. Однако для этой задачи нам также нужно log(x*x*x) == 3*log(x), а это не выполняется в двоичных числах с плавающей запятой. Я обновлю ответ. - person Eric Postpischil; 02.10.2013

Я думаю, вы хотите:

/* find the largest x where p^x < n, p is prime, n and x is natural numbers.
 */

define variable p as integer no-undo format ">,>>>,>>>,>>9".
define variable x as integer no-undo format ">>9".
define variable n as integer no-undo format ">,>>>,>>>,>>9".

define variable i as integer no-undo format "->>9".

define variable z as decimal no-undo format ">>9.9999999999".

update p n with side-labels.

/* approximate x
 */

z = log( n ) / log( p ).
display z.

x = integer( truncate( z, 0 )).  /* estimate x                */

/* is p^x < n ?
 */

if exp( p, x ) >= n then
  do while exp( p, x ) >= n:    /* was the estimate too high? */
    assign
      i = i - 1
      x = x - 1
    .
  end.
 else
  do while exp( p, x + 1 ) < n:  /* was the estimate too low? */
    assign
      i = i + 1
      x = x + 1
    .
  end.

display
  x skip
  exp( p, x ) label "p^x" format ">,>>>,>>>,>>9" skip
  i skip
  log( n ) skip
  log( p ) skip
  z skip
 with
  side-labels
.
person Tom Bascom    schedule 02.10.2013
comment
это создает ошибку, когда результат близок к целочисленному решению, что даст вам ложное срабатывание. - person Zesar; 07.10.2013
comment
Это никогда не вызывает ошибку. Оно не более чувствительно к ложным срабатываниям, чем любое другое предлагаемое решение. - person Tom Bascom; 07.10.2013
comment
если вы округлите значение в позиции 0, вы получите неправильное значение, например. log(9)/log(8) => 1, а не 0, как требуется по определению. когда я сказал ошибку, я имел в виду математику, извините за неконкретность ;-) - person Zesar; 07.10.2013
comment
отображение журнала (8) в формате 9.9999999999 журнала (9) в формате 9.9999999999 усечения ( округление ( журнал (9) / журнал (8), 0 ), 0 ) . 2,0794415417 2,1972245773 1,00 - person Tom Bascom; 07.10.2013
comment
@Mark: Возможно, вам следует перечитать свой собственный ответ? Вы говорите: «Отсутствуют какие-либо другие предпочтительные варианты, такие как другая функция округления..., ну, есть еще одна функция округления». Он называется ОКРУГЛ(). - person Tom Bascom; 07.10.2013
comment
@Mark - я понимаю комментарии о представлении действительных чисел. Очевидно, что простое добавление некоторой константы к результату не поможет правильно решить эти проблемы. Точно так же непонимание того, что функция Progress 4GL TRUNCATE() не совпадает с функцией ROUND(), также ничего не решит. - person Tom Bascom; 08.10.2013
comment
Учитывая, что LOG() является неточным и что ошибка в результате может приходиться на любую сторону от ближайшего целого числа, я собрал некоторый код, который проверяет p ^ x ‹ n и корректирует x вверх или вниз по мере необходимости (как предлагает Эрик) . Я также добавил несколько результатов отладки, чтобы вам было легче следить за вычислениями и видеть, что происходит. - person Tom Bascom; 08.10.2013

Корень проблемы в том, что функция журнала, подверженная ошибкам усечения с плавающей запятой, используется для решения вопроса в области натуральных чисел. Во-первых, я должен указать, что на самом деле в приведенном примере 1 действительно является правильным ответом. Мы ищем наибольший x такой, что p^x ‹ n; не p^x ‹= n. 2^1 ‹ 4, а 2^2 — нет. Тем не менее, у нас все еще есть проблема, потому что, когда p ^ x = n для некоторого x, log(n), деленное на log(p), вероятно, с таким же успехом могло бы оказаться немного выше целого числа, а не ниже, если только не существует какой-либо системной смещения в реализации логарифмической функции. Таким образом, в этом случае, когда есть некоторый x, для которого p^x=n, мы на самом деле хотим быть уверены, что округляем до следующего меньшего целого значения для x.

Таким образом, даже такое решение не решит эту проблему:

display  truncate(round(log(4) / log(2), 10) , 0) .

Я вижу два способа справиться с этим. Один из них похож на то, что вы уже пробовали, за исключением того, что, поскольку мы на самом деле хотим округлить до следующего меньшего натурального числа, мы будем вычитать, а не добавлять:

display  truncate(log(4) / log(2)  - 0.00000001, 0) .

Это будет работать до тех пор, пока n меньше 10 ^ 16, но более аккуратным решением было бы установить граничные условия с помощью фактической целочисленной математики. Конечно, это тоже не удастся, если вы доберетесь до чисел, превышающих максимальное целочисленное значение. Но если это не проблема, вы можете просто использовать свое первое решение, чтобы получить приблизительное решение:

display  truncate(log(4) / log(2) , 0) .

А затем проверьте, работает ли результат в уравнении p^x ‹ n. Если оно не меньше n, вычтите единицу и повторите попытку.

Между прочим, в определение натуральных чисел не входит ноль, поэтому, если наименьшее возможное значение для x равно 1, то наименьшее возможное значение для p^x равно p, поэтому, если n меньше или равно p, не существует решения в натуральных числах.

person Mark Bailey    schedule 07.10.2013

Большинство калькуляторов также не могут вычислить sqrt{2}*sqrt{2}. Проблема в том, что обычно у нас не так много десятичных знаков.

Обходной путь: избегайте TRUNCATE, используйте ROUND, например

 ROUND(log(4) / log(2), 0).

Round(a,b) округляет десятичное число a до ближайшего числа, имеющего b десятичных знаков.

person AD.    schedule 11.10.2013