Неточность в числе-> строка в схеме - PullRequest
0 голосов
/ 28 января 2020

Я работаю над программой Scheme, где в каком-то месте мне нужна пара счетчиков с плавающей запятой и тот же счетчик, что и у форматированной строки. У меня проблемы с преобразованием числа в строку.

Может кто-нибудь объяснить мне эти неточности в этом коде?

(letrec ((ground-loop (lambda (times count step)
             (if (= times 250)
              (begin
                (display "exit")
                (newline)
              ) 
              (begin 
                  (display (* times step)) (newline)
                  (display (number->string  (* times step)))(newline)
                  (newline)
                  (newline)
                  (ground-loop (+ times 1) (* times step) step)
                )
             )
          )
))
  (ground-loop 0 0 0.05)
)

Часть вывода выглядит так

7,25 7,25

7,3 7,300000000000001

7,35 7,350000000000001

7,4 7,4

7,45 7,45

7,5 7,5

7,55 7,550000000000001

7,6 7,600000000000001

7,65 7,65

Я знаю о неточностях с плавающей запятой и пробовал несколько форм увеличения счетчика, но проблема в самом преобразовании.

Есть идеи для легкого исправления? Попробовал немного с явно округленными числами, но это не помогло. Результаты даже варьируются от IDE и среды к среде. Действительно ли я должен выполнять манипуляции со строками после преобразования? Очень странная вещь в моем случае - получить точную цифру c, но строка выключена.

Спасибо

1 Ответ

2 голосов
/ 28 января 2020

Мне кажется, что:

  • нативный тип с плавающей точкой (тип, который вы получите, прочитав 1.0) вашей реализации - IEEE double float;
  • the display вашей Схемы не печатает такие поплавки «правильно» (см. Ниже, я не уверен, что это означает, что он глючит);
  • ваш number->string делает правильные вещи.

Под словом «правильно» я имею в виду «таким образом, чтобы при чтении напечатанного display получалось эквивалентное число». Я совсем не уверен, что display требуется , чтобы быть корректным в этом ограничительном смысле, поэтому я не уверен, является ли это ошибкой. Кто-то, кто понимает стандарты Схемы лучше, чем я, может прокомментировать это.

В частности, если родной тип с плавающей точкой языка является двойным с плавающей точкой IEEE, то, например:

(= (* 0.05 3) 0.15)

является ложным, как и

(= (* 0.05 146) 7.3)

Вот пример, который у вас есть в первой строке вашего вывода.

Так что вы, конечно, не должны предполагать, что ваша программа когда-либо выдаст число, равное числу, которое вы получите, прочитав, например, 7.3, потому что оно не будет.

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

Хорошо, у меня есть реализация на Лиспе, которая это надежно об этом. В этой системе по умолчанию используется формат с плавающей запятой IEEE с одинарной точностью, и я могу заставить читателя читать двойные с плавающей точкой, например, 1.0d0. Итак, в этой реализации вы можете увидеть результаты:

> (* 0.05d0 3)
0.15000000000000002D0

> (* 0.05d0 146)
7.300000000000001D0

И вы увидите, что это именно то (с точностью до индикатора двойной точности), что number->string дает вам, а нет что display дает вам.

Если вы хотите получить представление числа таким образом, чтобы при чтении оно получило эквивалентное число, тогда number->string - это то, что ты должен доверять В частности, R5RS говорит в разделе 6.2.6, что:

(let ((number number)
      (radix radix))
  (eqv? number
        (string->number (number->string number
                                        radix)
                        radix)))

- истина, и «это ошибка, если ни один из возможных результатов не делает это выражение истинным».

Вы можете проверить поведение number->float & float->number в диапазоне чисел, например (это может предполагать более новую или полную схему, чем у вас):

(define (verify-float-conversion base times)
  (define (good? f)
    (eqv? (string->number (number->string f)) f))
  (let loop ([i 0]
             [bads '()])
    (let ([c (* base i)])
      (if (>= i times)
          (values (null? bads) (reverse bads))
          (loop (+ i 1) (if (good? c) bads (cons c bads)))))))

Тогда вам следует get

> (verify-float-conversion 0.05 10000)
#t
()

В более широком смысле использование чисел с плавающей запятой, еще больше операций с плавающей запятой, которые являются результатом более сложных вычислений, чем чтение их из какого-либо входного источника, поскольку уникальные индексы в любой табличной структуре чреваты опасностью для ее размещения. довольно мягко: ошибки с плавающей точкой означают, что просто очень опасно предполагать, что (= a b) верно для чисел с плавающей запятой, даже если математически это должно быть.

Если вы хотите, чтобы такие индексы делали точную арифметику c, и конвертируйте результаты этой арифметики c в числа с плавающей точкой в ​​той точке, в которой вам необходимо выполнить вычисления. Я считаю (но не уверен), что реализации Scheme в настоящее время требуются для поддержки точной рациональной арифметики c (конечно, это, похоже, верно для R6RS), поэтому, если вы хотите считать 20-е (скажем), вы можете сделать это, считая в единицы 1/20, что является точным, и затем строит числа с плавающей точкой, когда они вам нужны.

Вероятно, безопаснее сравнивать числа с плавающей точкой в ​​том случае, если вы, например, сравниваете число с плавающей точкой, полученное при использовании некоторого начального числа с плавающей точкой значение и умножение его на целое число машины и сравнение его с какой-то более ранней версией, которую вы прочитали string->number. Но если ваши вычисления более сложны, чем это, вам нужно быть очень осторожным.

...