How to use double-float?

2019-04-24 18:49发布

问题:

I am struggling a little trying to figure out how to tell Lisp that I want to use double-float values. Suppose I have:

(let ((x 1)) (format t "~A~%" (/ x 3.0)))

Which gives:

0.33333334

If I want to use double-float, I tried this:

(let ((x 1)) (declare (type double-float x)) (format t "~A~%" (/ x 3.0)))
0.33333334

So the result is not a double-float. I can, however, force double-float like this:

(let ((x 1)) (format t "~A~%" (/ x 3.0d0)))
0.3333333333333333d0

And now I get a double-float result.

So my question is: if I'm defining a form or function in which I want arithmetic to be in double-float, how do I establish that? I've read lots of online resources on using declare, proclaim, etc, but haven't been able to apply it to getting the result I'm after. I'm not convinced I know how to utilize these in this context, or even if they are the correct mechanism to use.

Same question would apply if I were trying to do long-float or anything else that isn't the default.

回答1:

If you want to compute with a special float format you have to tell it. Usually if you divide double-floats, the result will be a double float. If you have constants, you need to denote them as such.

The Common Lisp standard says: The result of a numerical function is a float of the largest format among all the floating-point arguments to the function..

The interpretation of the following depends on a few things. It depends on how the reader reads numbers. For integers the base can be specified. For floats it depends on the default float format.

(let ((x 1)) (format t "~A~%" (/ x 3.0)))

Let's see how *read-default-float-format* affects it:

CL-USER 9 > *read-default-float-format*
SINGLE-FLOAT

CL-USER 10 > (let ((x 1)) (format t "~A~%" (/ x 3.0)))
0.33333334
NIL

CL-USER 11 > (setf *read-default-float-format* 'double-float)
DOUBLE-FLOAT

CL-USER 12 > (let ((x 1)) (format t "~A~%" (/ x 3.0)))
0.3333333333333333
NIL

Also note that you can specify the type for literal numbers by using an exponent marker:

  • d = double-float
  • e = float of *read-default-float-format*
  • f = single-float
  • l = long-float
  • s = short-float

Example:

CL-USER 15 > (setf *read-default-float-format* 'single-float)
SINGLE-FLOAT

CL-USER 16 > (let ((x 1)) (format t "~A~%" (/ x 3.0d0)))
0.3333333333333333D0
NIL

You can also coerce numbers to a certain type. The function COERCE makes it explicit which type you mean:

CL-USER 17 > (let ((x 1))
               (format t "~A~%" (/ (coerce x 'double-float) 3.0)))
0.3333333333333333D0
NIL


回答2:

As you noted you can type d0 after the number. E.g.,

* 3.0d0
; => 3.0d0
* (type-of 3.0d0)
;=> DOUBLE-FLOAT

However, that's the literal notation for a double float, just like 1 is the literal notation for an integer. You can customize the default type of floating point numbers from the reader with *read-default-float-format*, and Rainer Joswig's answer shows how. The declaration

(declare (type double-float x))

is a promise to the compiler that the value of the variable x is a double float. You're lying to the compiler. To get a double float, you'll need to either write one as a literal (e.g., 1.0d0) or convert one with the float function:

* (float 1 0.0d0)
;=> 1.0d0

You could also use coerce here:

* (coerce 1 'double-float)
;=> 1.0d0

When there's an option, it's reasonable to compare them. In SBCL, it turns out that these two options actually compile to the same thing:

CL-USER> (disassemble (compile nil (lambda (x) (coerce x 'double-float))))
; disassembly for (LAMBDA (X))
; 039C33E8:       488BD6           MOV RDX, RSI               ; no-arg-parsing entry point
;      3EB:       488B059EFFFFFF   MOV RAX, [RIP-98]          ; #<FDEFINITION object for SB-KERNEL:%DOUBLE-FLOAT>
;      3F2:       B908000000       MOV ECX, 8
;      3F7:       FF7508           PUSH QWORD PTR [RBP+8]
;      3FA:       FF6009           JMP QWORD PTR [RAX+9]
;      3FD:       CC0A             BREAK 10                   ; error trap
;      3FF:       02               BYTE #X02
;      400:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
;      401:       54               BYTE #X54                  ; RCX
NIL
CL-USER> (disassemble (compile nil (lambda (x) (float x 0.0d0))))
; disassembly for (LAMBDA (X))
; 03BC5B18:       488BD6           MOV RDX, RSI               ; no-arg-parsing entry point
;       1B:       488B059EFFFFFF   MOV RAX, [RIP-98]          ; #<FDEFINITION object for SB-KERNEL:%DOUBLE-FLOAT>
;       22:       B908000000       MOV ECX, 8
;       27:       FF7508           PUSH QWORD PTR [RBP+8]
;       2A:       FF6009           JMP QWORD PTR [RAX+9]
;       2D:       CC0A             BREAK 10                   ; error trap
;       2F:       02               BYTE #X02
;       30:       18               BYTE #X18                  ; INVALID-ARG-COUNT-ERROR
;       31:       54               BYTE #X54                  ; RCX
NIL

It sounds like you're trying to do some automatic conversion, and I don't think you're going to find a way to do that. If you've got a number coming in, and you want a double-float you'll have to convert it yourself. If you want to check that a value coming in is a double-float you might have some luck with declarations, but a type declaration is just a promise to the compiler that something will have a particular type; this usually means that the compiler can omit checks since it's been promised that the value will have a certain type. That said, you might have some luck; in SBCL:

> (defun foo (x)
    (declare (double-float x))
    (+ x 2.0d0))

> (foo 3)
; The value 3 is not of type DOUBLE-FLOAT.
;    [Condition of type TYPE-ERROR]

You might get different results if you change the safety optimization, though. If you want to ensure a type check, then use check-type:

> (defun foo (x)
    (check-type x double-float)
    (+ x 2.0d0))

> (foo 3)
; The value of X is 3, which is not of type DOUBLE-FLOAT.
;    [Condition of type SIMPLE-TYPE-ERROR]