Scheme 中的数字-> 字符串不准确

Inaccuracy in number->string in Scheme

我正在开发一个 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 和环境而异。我真的必须在转换后进行字符串操作吗? 在我的例子中,非常奇怪的事情是有一个精确的数字结果,但字符串是关闭的。

谢谢

在我看来好像:

  • 您的实现的本机浮点类型(您通过阅读 1.0 获得的类型)是 IEEE 双浮点;
  • 你的 Scheme 的 display 没有打印这样的浮点数 'correctly'(见下文,我不确定这是否意味着它有问题);
  • 你的number->string做正确的事。

上面的 'correctly' 是指 'in a way so that reading what display printed returns an equivalent number'。但是,我完全不确定 display 是否需要 在这种限制性意义上是正确的,所以我不确定这是否是一个错误。比我更了解 Scheme 标准的人可能会对此发表评论。

特别是如果语言的原生浮点类型是 IEEE 双精度浮点,那么,例如:

(= (* 0.05 3) 0.15)

为假,

为假
(= (* 0.05 146) 7.3)

你输出的第一行中的例子是什么。

所以你当然不应该假设你的程序会产生一个等于你通过阅读 7.3 得到的数字,因为它不会。

在上面我小心地避免打印出数字,那是因为我不确定 display 在这方面是否可靠,尤其是我不确定 你的 display 是可靠的或必须的。

嗯,我手头有一个 Lisp 实现,在这方面是可靠的。在这个系统中,默认的浮点数格式是单精度 IEEE 浮点数,我可以获得 reader 来读取双精度浮点数,例如 1.0d0。所以,在这个实现中你可以看到结果:

> (* 0.05d0 3)
0.15000000000000002D0

> (* 0.05d0 146)
7.300000000000001D0

你会发现这些正是(取决于双精度指标)number->string 给你的,不是 display 给你的给你了。

如果您想做的是以读取它的方式获得数字的表示 return 一个等效数字,那么 number->string 是您应该信任的。特别是 R5RS 在第 6.2.6 节中说:

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

为真,'it is an error if no possible result makes this expression true'.

您可以检查 number->floatfloat->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)))))))

那么你应该得到

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

更普遍地使用浮点数,更多的浮点数是一些计算的结果,比从一些输入源中读取它们更复杂,因为任何类型的表格结构的唯一索引都充满了危险,说得温和一点:浮动-点错误意味着假设 (= a b) 对于浮点数是真的是非常危险的,即使它在数学上应该是这样。

如果您希望此类索引改为执行精确算术,并在您需要进行计算时将该算术的结果转换为浮点数。我相信(但我不确定)现在需要 Scheme 实现来支持精确的有理算术(当然这对于 R6RS 来说似乎是正确的),所以如果你想计算 20ths(比如说)你可以通过以 1/20,这是准确的,然后在需要时构造浮点数。

比较浮点数可能是安全的,例如,如果你正在比较一个浮点数,你通过取一些初始浮点值并将它乘以一个机器整数并将它与你拥有的它本身的一些早期版本进行比较string->number 阅读。但如果你做的计算比这更复杂,你需要非常小心。