How to use double-float?
Asked Answered
U

2

9

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.

Unaccomplished answered 25/1, 2014 at 16:56 Comment(9)
See https://mcmap.net/q/1173624/-common-lisp-type-declarations-not-working-as-expected/1193075Myocardium
@Myocardium that's a great link, but it doesn't really answer my question. It indeed tells me what declare means, but doesn't tell me how to do what I'm wanting to do: which is to assure that the variables/calculations are double-float rather than the default.Unaccomplished
Yes that's why it's a comment and not an answer ;-)Myocardium
@Myocardium that's cool. Although "just a link" wouldn't be a suitable answer according to SO guidelines anyway. ;) Seriously, I am very glad you provided that link as it does explain some things that I wasn't totally gathering from the online documentation I was reading.Unaccomplished
it's a bit different in CL - i.e. the opposite of what you expected (I think). when you declare something to be a certain type, you are telling the compiler that you will take care for that variable to contain that specific type of value. The compiler won't take any actions to ensure this, on the contrary, it will assume that the type is as declared. Next, understand contagion - as the quote in Rainer Joswig's answer describes. :)Milden
Thanks @WillNess, I was, in fact, just reading a little something about "contagion" (relative to Lisp). I shall investigate further.Unaccomplished
@WillNess so in CL that if I had a function that did some calculations based upon parameters, and it had no constants in it, then unless I used coerce everywhere, it would determine precision based upon the precision of the parameters that I pass? And if those are variables, then the precision of those would depend upon what how those were previously computed?Unaccomplished
yes, though not necessarily everywhere but in some strategic places. The data type "contagion" (maybe it's a C terminology? I don't remember) is just what the quote shown by Rainer is saying: a float "infects" an otherwise ints-only expression: [1]> (/ 1 7) => 1/7 [2]> (/ 1 7.0) => 0.14285715. And "precision" is a whole other game, you'd have to track the deltas etc. I know, you meant types. :) So where it's important, do use coerce, or some (+ ... 0.0d0) tricks etc. -- CL is much more a VM than a language, by attitude.Milden
one can even say, I think, that CL is the most sophisticated assembler, ever!Milden
G
11

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
Genarogendarme answered 25/1, 2014 at 17:54 Comment(1)
Thanks Rainer. I had read about *read-default-float-format* and for some stupid reason thought it only applied to how Lisp casted floats that were read from user input.Unaccomplished
P
4

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]
Pancreatin answered 25/1, 2014 at 17:29 Comment(4)
So if I have a function that doesn't involve any constants at all, then the only way to get it to calculate in double float is to pass it a constant or variable that is already assured to be a double-float? Or is coerce something I should use (as @RainerJoswig shows in one of his examples)? Or is coerce really only to be used in exceptional cases?Unaccomplished
@mbratch You can use coerce, too. I wasn't sure whether one would be more efficient (coerce is certainly more general purpose), but it turns out that in SBCL they compile to the same code. I've updated the answer with an example.Pancreatin
Thanks a lot (+1). I had to give the green check to Rainer because he was the first to point out coerce and *read-default-float-format* but it was a tough call.Unaccomplished
@mbratch heh, no worries. I didn't even think of *default-read-float-format* until I saw it in his answer. I like that it's got more about the general process of how floats are read, and what you can do with it.Pancreatin

© 2022 - 2024 — McMap. All rights reserved.