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.
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
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]