-
Notifications
You must be signed in to change notification settings - Fork 0
/
float-a.lisp
914 lines (724 loc) · 37.1 KB
/
float-a.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
; ACL2 Version 8.6 -- A Computational Logic for Applicative Common Lisp
; Copyright (C) 2024, Regents of the University of Texas
; This version of ACL2 is a descendent of ACL2 Version 1.9, Copyright
; (C) 1997 Computational Logic, Inc. See the documentation topic NOTE-2-0.
; This program is free software; you can redistribute it and/or modify
; it under the terms of the LICENSE file distributed with ACL2.
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; LICENSE for more details.
; Written by: Matt Kaufmann and J Strother Moore
; email: [email protected] and [email protected]
; Department of Computer Science
; University of Texas at Austin
; Austin, TX 78712 U.S.A.
(in-package "ACL2")
; See also float-b.lisp, as discussed in the comment that concludes this file.
; Essay on Support for Floating-point (double-float, df) Operations in ACL2
; Much of what we have to say about dfs is in :DOC df. This Essay assumes
; familiarity with that :DOC topic and adds implementation-level remarks.
; Note: A tags-search for "[^a-z]df[^a-oq-z]" outside doc.lisp will hit "df"
; occurrences relevant to support for floating-point operations.
; Section: *1* functions take ACL2 objects
; A key to evaluation with dfs is that *1* functions never take or return
; Common Lisp floats. This complicates ec-call a bit for raw Lisp calls when
; dfs are involved, but that is solved by requiring a :dfs-in or :dfs-out
; argument in any such ec-call expression.
; Section: On translation for execution
; The introduction of dfs after Version_8.5 affected translation for execution,
; by enforcing stobj-like syntactic restrictions when expressions may involve
; df inputs and outputs. This is particularly tricky for mv, let, and mv-let
; expressions.
; First consider a call of translate11 on (mv expr1 ... exprk). If the
; stobjs-out argument of translate11 is a list (mv x1 ... xk), then translation
; presents no difficulty; just translate expri with stobjs-out (xi). But when
; stobjs-out is a symbol for that translate11 call, we have a challenge.
; Before the addition of dfs, this case was simple: each expri that is a stobj
; name would translate to itself (and that is still the case), and every other
; expri would translate with stobjs-out (nil). But now, some of the non-stobj
; expri should be translated with stobjs-out (:DF). Which ones?
; In that case, we guess which expressions should be treated as dfs (i.e.,
; translated with stobjs-out = (:DF)) using function compute-stobj-flags-df?
; (and its subroutine returns-df?). That attempt to guess may occasionally
; fail, for example when the expression is a recursive call of a function F
; currently being defined and translation is with stobjs-out = F. In that case
; the special value :df? is put into the stobjs-out list being constructed.
; That list of nil, :df, :df?, and stobj values is passed to
; translate11-lst/stobjs-out, which returns not only the usual three
; error/list/bindings results but also (in the non-error case) the stobjs-out
; for which translation is successful. Then translate-bind assigns that
; returned stobjs-out to F in the returned bindings.
; Next consider translation of (let ((v1 expr1) ... (vk exprk)) <body>). Here
; the problem is similar to translation of (mv expr1 ... exprk): which expri
; should be treated as dfs, i.e., translated with stobjs-out = (:DF)? The
; solution is similar to the one for mv expressions. For let, we use function
; compute-stobj-flags-df?-doublets (which too calls returns-df?) to guess which
; expri are dfs, but again, some guesses may fail. So as for mv, in this case
; translate11-lst/stobjs-out is called on the list (expr1 ... exprk), which
; returns not only the usual three error/list/bindings results but also the
; stobjs-out for which translation is successful (in the non-error case). That
; returned stobjs-out is used, together with double-float type declarations of
; the let form (not shown above), in determining which vi are known-dfs for
; translation of <body>.
; Finally, consider translation for execution of (mv-let (v1 ... vk) <expr>
; <body>). Before there were dfs, we could construct a suitable stobjs-out for
; translating <body> even before translating <expr>: that stobjs-out is (s1
; ... sk), where si is vi if vi is a stobj name, and otherwise si is nil. But
; now, when vi is not a stobj name then si might be either nil or :DF. To
; determine which, translate11-mv-let calls translate11-collecting-known-dfs,
; which returns a suitable known-dfs for translation of <body>. See
; translate11-mv-let for details.
; Section: On overflow and soundness
; This section is about a concern raised by Pete Manolios and Eric Smith in a
; talk on 2/8/2024, which amounts to the question of whether overflow with a df
; operation could result in a value that violates the axioms, by returning
; other than a double-float. In short, we think that the Common Lisp
; specification guarantees the absence of such a disaster because of the
; following Key Passage from the Common Lisp Hyperspec.
; [Key Passage]
; 12.1.4.4 Rule of Float Precision Contagion
; The result of a numerical function is a float of the largest format among
; all the floating-point arguments to the function.
; That passage seems to rule out the possibility that, for example, (* x y)
; could evaluate to the string "nan" or the symbol 'nan, thus violating the
; provable theorem (dfp (df* x y)).
; Even without that Key Passage, we find it very unlikely that any Common Lisp
; implementer would return a non-float upon overflow. See :DOC
; generalized-booleans for discussion of an analogous issue, where the CL spec
; does not tie things down but we pretend that it does.
; The remainder of this Section provides alternate justification for ACL2's
; assumption that floating-point overflow either causes an error or returns a
; double-float value, without depending on the Key Passage above.
; (So perhaps the remainder of this Section is unnecessary. But it provided
; our initial justification, it contains relevant information, and it provides
; backup in case there are (unforeseen) challenges to the relevance of the Key
; Passage.)
; We start with the following quote from HyperSpec Section
; 12.1.4.3, "Rule of Float Underflow and Overflow"
; https://www.lispworks.com/documentation/HyperSpec/Body/12_adc.htm).
; An error of type floating-point-overflow or floating-point-underflow
; should be signaled if a floating-point computation causes exponent
; overflow or underflow, respectively.
; The notion "should be signaled" is found in Section 1.4.2 of the Hyperspec,
; "Error Terminology", in particular the quote below. The key here is that
; since ACL2 uses optimization setting (SAFETY 0), it presumably runs "unsafe
; code", so the consequences of overflow are unpredictable. Thus (* x y) might
; evaluate to "nan", say, if we have overflow -- at least in principle.
; An error should be signaled
;
; This means that an error is signaled in safe code, and an error
; might be signaled in unsafe code. Conforming code may rely on the
; fact that the error is signaled in safe code. Every implementation
; is required to detect the error at least in safe code. When the
; error is not signaled, the ``consequences are undefined'' (see
; below). For example, ``+ should signal an error of type type-error
; if any argument is not of type number.''
;
; The consequences are undefined
;
; This means that the consequences are unpredictable. The consequences
; may range from harmless to fatal. No conforming code may depend on
; the results or effects. Conforming code must treat the consequences
; as unpredictable. In places where the words ``must,'' ``must not,''
; or ``may not'' are used, then ``the consequences are undefined'' if
; the stated requirement is not met and no specific consequence is
; explicitly stated. An implementation is permitted to signal an error
; in this case.
;
; For example: ``Once a name has been declared by defconstant to be
; constant, any further assignment or binding of that variable has
; undefined consequences.''
; The following from the CCL manual Section 4.11, "Floating Point Numbers"
; (https://ccl.clozure.com/manual/chapter4.11.html) is reasonably explicit that
; floating-point overflow causes an error (note in particular the second
; sentence).
; Floating-point exceptions are generally enabled and detected. By
; default, threads start up with overflow, division-by-zero, and
; invalid enabled, and the rounding mode is set to nearest. The
; functions SET-FPU-MODE and GET-FPU-MODE provide user control over
; floating-point behavior.
; The situation seems to be similar for SBCL, though we have not found a
; documented guarantee. SBCL source file src/code/float-trap.lisp led us to
; produce the following log using ACL2 built on SBCL, after dropping into raw
; lisp.
; * (sb-int:get-floating-point-modes)
; (:TRAPS (:OVERFLOW :INVALID :DIVIDE-BY-ZERO) :ROUNDING-MODE :NEAREST
; :CURRENT-EXCEPTIONS (:INEXACT) :ACCRUED-EXCEPTIONS (:INEXACT) :FAST-MODE NIL)
; *
; Indeed, for SBCL we check at build time that :OVERFLOW is among the :TRAPS.
; So, it looks like both CCL and SBCL produce fp overflow exceptions by
; default. Both provide a way to change that, but of course ACL2 wouldn't make
; such a capability available (without a trust tag).
; We haven't done similar investigations for the other Lisps that can host
; ACL2. But we can already imagine that SBCL may cause (* x y) to evaluate to
; "nan", say, by changing the initial floating-point-modes. So further
; investigation won't lock down a guarantee of floating-point overflow errors
; for all host Lisps (if we didn't have the Key Passage noted above).
; We could address this concern by having ACL2 do one of the following, at
; least for SBCL and other Lisps that don't have documented guarantees like the
; one for CCL.
; - Check at runtime that df+ etc. produce a double-float;
; OR
; - Change the guards of df+ etc. to check that the arguments won't cause
; overflow.
; We are concerned that the former could non-trivially hurt performance, while
; the latter would take some effort to implement and could cause headaches for
; users.
; Instead, we arrange for ACL2 to check at build time that (*
; most-positive-double-float most-positive-double-float) signals an error or
; returns a double-float (presumably an infinity, as we have seen in LispWorks
; and Allegro CL). While that check does not provide an ironclad guarantee, it
; does provide extra confidence; and anyhow, as noted above, we find it very
; unlikely that floating-point overflow would ever return a value that violates
; ACL2 axioms (even if we didn't have the Key Passage shown above).
; By the way, even if overflow errors were somehow defeated, the following logs
; provide additional evidence that the values returned would still be
; consistent with the ACL2 axioms. These logs are with SBCL and CCL as they
; start up, without an ACL2 build.
; SBCL
; * double-float-positive-infinity
; #.DOUBLE-FLOAT-POSITIVE-INFINITY
; * most-positive-double-float
; 1.7976931348623157d308
; * (< most-positive-double-float double-float-positive-infinity)
; T
; * (type-of double-float-positive-infinity)
; DOUBLE-FLOAT
; * (rational most-positive-double-float)
; 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
; * (rational double-float-positive-infinity)
;
; debugger invoked on a SIMPLE-ERROR in thread
; #<THREAD "main thread" RUNNING {1001D60003}>:
; Can't decode NaN or infinity: #.DOUBLE-FLOAT-POSITIVE-INFINITY.
;
; Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
;
; restarts (invokable by number or by possibly-abbreviated name):
; 0: [ABORT] Exit debugger, returning to top level.
;
; (SB-KERNEL:INTEGER-DECODE-DOUBLE-FLOAT #.DOUBLE-FLOAT-POSITIVE-INFINITY)
; 0]
; CCL
; ? ccl::double-float-positive-infinity
; 1D++0
; ? most-positive-double-float
; 1.7976931348623157D+308
; ? (< most-positive-double-float ccl::double-float-positive-infinity)
; T
; ? (type-of ccl::double-float-positive-infinity)
; DOUBLE-FLOAT
; ? (rational most-positive-double-float)
; 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
; ? (rational ccl::double-float-positive-infinity)
; 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216
; ? (> (rational ccl::double-float-positive-infinity)
; (rational most-positive-double-float))
; T
; ?
#-acl2-loop-only
(declaim (inline
binary-df*
binary-df+
binary-df-log
binary-df/
df-abs-fn
df-acos-fn
df-acosh-fn
df-asin-fn
df-asinh-fn
df-atan-fn
df-atanh-fn
df-cos-fn
df-cosh-fn
df-exp-fn
df-expt-fn
df-pi
df-rationalize
df-string
df-sin-fn
df-sinh-fn
df-sqrt-fn
df-tan-fn
df-tanh-fn
dfp
from-df
; to-df ; macro in raw Lisp
unary-df-
unary-df/
unary-df-log
df<-fn
df=-fn
df/=-fn
df0
df1
df-minus-1
))
(encapsulate () (logic)
; We use a surrounding encapsulate for logic mode, rather than of putting
; (logic) inside the partial-encapsulate, to support redundancy in pass 2.
(partial-encapsulate
(((constrained-to-df *) => * :formals (x) :guard (rationalp x)))
nil
; We describe here the implicit axioms of this partial-encapsulate as well as a
; local witness function, WDF, that satisfies those axioms and the explicit
; axioms below. We introduce WDF abstractly, not in code, to be the required
; local witness for a partial-encapsulate that satisfies both the explicit and
; implicit axioms of the partial-encapsulate. Throughout this discussion we
; imagine a fixed Lisp implementation.
; We consider computations C(x,y) that compute (rational (float x 0.0D0)) = y.
; Such C(x,y) is "actually performed" if that computation is actually performed
; by our fixed Lisp implementation, possibly in the past, possibly in the
; future. Note that C(0,0) is actually performed, by a sanity check in
; acl2.lisp.
; Informally, we want to define WDF to specify not only the computations C(x,y)
; that are actually performed but also to "close" when computing on -x or on y.
; We make that idea precise by defining when a computation C(x,y) -- that is,
; computing (rational (float x 0.0D0)) for rational x to yield y -- is
; considered to be a "WDF computation": namely, if at least one of the
; following criteria are met.
; [WDF1] C(x,y) is actually performed.
; [WDF2] C(-x,z) is actually performed for some z.
; [WDF3] C(x,y) when y = x {capturing computations where x is representable}
; Note that since (float x 0.0D0) has finite range and the function, rational,
; is injective on double-floats, there are only finitely many WDF computations,
; in spite of [WDF3].
; We now define WDF with a giant case statement that maps x to y for every WDF
; computation C(x,y) and maps everything else to 0. Note that the argument
; just above shows that WDF is idempotent: the only remaining step is to check
; that (WDF (WDF r)) = (WDF r) for r not equal to any such x, but in that case
; we have (WDF (WDF r)) = (WDF 0) = 0 = (WDF r).
; The implicit axioms of this partial-encapsulate consist of all formulas
; (equal (constrained-to-df x) y) for WDF computations C(x,y).
; Comments in the theorems below argue that those theorems are satisfied by
; the witness, WDF.
; Since to-df is defined logically to be constrained-to-df, we also need to
; know that computations performed by the under-the-hood raw Lisp code for
; to-df are justified by the axioms. More precisely, if (to-df r) evaluates to
; s, it must be the case that (equal (to-df r) s) is a theorem. But that
; equality is in fact one of the implicit axioms of the partial-encapsulate, by
; the raw Lisp definition of to-df and by [WDF1] above.
(local (defun constrained-to-df (x)
; This is not the true witness for the partial encapsulate, which is WDF as
; defined in comments above.
(declare (ignore x))
0))
(defthm rationalp-constrained-to-df
; The local witness WDF defined above always returns a rational.
(rationalp (constrained-to-df x))
:rule-classes :type-prescription)
(defthm constrained-to-df-idempotent
; Let's call the following identity, quoting
; http://www.lispworks.com/documentation/HyperSpec/Body/f_ration.htm, the
; "float-rational identity".
; It is always the case that
;
; (float (rational x) x) == x
; Now we can show that this axiom holds for the witness, WDF, to
; constrained-to-df. If (WDF x) = 0 then (WDF (WDF x)) = (WDF 0), which is 0
; since as noted above, C(0,0) is actually performed in a sanity check in
; acl2.lisp. So suppose (WDF x) is not 0, in which case x is one of the values
; handled by the giant case statement defining WDF. Then:
; (WDF (WDF x))
; = {by definition of WDF}
; (rational (float (rational (float x 0.0D0)) 0.0D0))
; = {since the second argument of float only specifies the type}
; (rational (float (rational (float x 0.0D0)) (float x 0.0D0)))
; = {by the float-rational identity applied to (float x 0.0D0) for x}
; (rational (float x 0.0D0))
; = {by definition of WDF}
; x
(equal (constrained-to-df (constrained-to-df x))
(constrained-to-df x)))
(defthm to-df-minus
; The HyperSpec section on "System Class FLOAT"
; (http://www.lispworks.com/documentation/HyperSpec/Body/t_float.htm) is not as
; helpful as it might be; in particular, "integer between b^p-1 and b^p-1"
; doesn't make any sense (maybe a minus sign is missing). But we take that
; section to say that we can view a float in the usual way (especially when
; IEEE 754 is respected, which we may assume since we insist on the presence of
; feature :ieee-floating-point at build time). So we take the HyperSpec to say
; that a double-float may be written either as 0.0 or as s*f*2^e where s is +1
; or -1, f is a positive integer, and e is an integer.
; [Aside: We use base 2 above. That isn't explicitly specified in the
; HyperSpec section on "System Class FLOAT". However, section "Basic and
; interchange formats" in the IEEE 754 spec
; (https://en.wikipedia.org/wiki/IEEE_754#CITEREFIEEE_7542019) has a table
; showing that the radix is 2 for double-precision. We give this a little test
; in acl2.lisp by checking that (float-radix 1.0D0) = 2.]
; Now we show that WDF, as defined above, satisfies this defthm. The
; hypothesis for WDF, (and (rationalp x) (equal (WDF x) x)), implies that x is
; a rational for which the following holds in Lisp.
; [1] (rational (float x 0.0D0)) = x.
; To show that the conclusion holds for WDF, it suffices to show the following,
; since by clause [WDF2] in the definition of WDF, (WDF (- x)) = (rational
; (float (- x) 0.0D0)).
; [*] (rational (float (- x) 0.0D0)) = (- x).
; To show [*], we use our interpretation above of the HyperSpec to choose s,
; f, and e as above so that (float x 0.0D0), whose rational value is x by [1],
; is realized as s*f*2^e. So x = s*f*2^e, which we can state as follows.
; [2] x is represented as s*f*2^e.
; Here is an immediate consequence of [2] by simple math.
; [3] -x is represented as (-s)*f*2^e.
; The function (float number prototype) is specified by the HyperSpec
; (http://www.lispworks.com/documentation/HyperSpec/Body/f_float.htm) as
; follows.
; If a prototype is supplied, a float is returned that is mathematically
; equal to number but has the same format as prototype.
; That is clearly nonsense if the given number is not representable! For
; example, the sentence displayed just above implies that the common value of
; the two float expressions just below is equal to both 11184811/33554432 and
; 1/3, which would imply the absurd conclusion that these two rationals are
; equal.
; ? (= (float 11184811/33554432 0.0f0) (float 1/3 0.0f0))
; T
; ?
; But if the HyperSpec sentence above means anything, it should hold for
; representable numbers, which we can state as follows.
; [4] If a rational x is represented by the double-float s*f*2^e, then
; (rational (float x 0.0D0)) is equal to x.
; Then [*] follows immediately from [3] by applying [4] to -x.
(implies (and (rationalp x)
(equal (constrained-to-df x) x)) ; basically, (dfp x)
(equal (constrained-to-df (- x))
(- x))))
(defthm constrained-to-df-default
; WDF, as defined above, clearly satisfies this property.
(implies (not (rationalp x))
(equal (constrained-to-df x) 0)))
(defthm constrained-to-df-0
; As noted above, this is justified by a check in acl2.lisp, so we might as
; well make this constraint explicit.
(equal (constrained-to-df 0) 0))
(defthm constrained-to-df-monotonicity
; Why do we include this theorem, and why is it justified?
; To see why we include it, consider the following example where guard
; verification fails but could otherwise succeed.
; (defun foo (n)
; (declare (xargs :guard (and (integerp n) (<= 1 n))))
; (df/ (to-df n)))
; The following goal, generated for guard verification, is provable using this
; monotonicity property, but is likely not provable without it.
; (IMPLIES (AND (<= 1 N) (INTEGERP N))
; (NOT (EQUAL (TO-DF N) 0)))
; But what justifies this property? To-df is defined in raw Lisp as,
; essentially, (float x 0.0D0). The CL HyperSpec says the following about
; float.
; > float number &optional prototype => float
; > ...
; > If a prototype is supplied, a float is returned that is
; > mathematically equal to number but has the same format as prototype.
; That is, of course, incorrect. There's no way, for example, that (float 1/3
; 0.0d0 could be mathematically equal to 1/3, since 1/3 isn't representable by
; a double-float. CLtL2, specifically Section 12.6 on "Type Conversions...",
; is also not helpful; it says that float converts, but it says nothing about
; the value of the resulting floating-point value.
; However, it would be utter craziness if x < y but float reverses the order.
; An additional argument is that evaluation of (float x 0.0d0) quite plausibly
; gives the same result as (coerce x 'double-float). The following HyperSpec
; passage about coerce (see
; https://www.lispworks.com/documentation/HyperSpec/Body/f_coerce.htm#coerce)
; seems to imply that (coerce x 'double-float) can't reverse order.
; If the result-type is any of float, short-float, single-float,
; double-float, long-float, and the object is a real, then the result
; is a float of type result-type which is equal in sign and magnitude
; to the object to whatever degree of representational precision is
; permitted by that float representation. (If the result-type is float
; and object is not already a float, then the result is a single
; float.)
(implies (and (<= x y)
(rationalp x)
(rationalp y))
(<= (constrained-to-df x) (constrained-to-df y)))
:rule-classes (:linear :rewrite))
)
)
#+acl2-loop-only
(defun to-df (x)
(declare (xargs :guard (rationalp x)
:mode :logic))
(constrained-to-df x))
(defthm rationalp-to-df
(rationalp (to-df x))
:rule-classes :type-prescription)
(defthm to-df-idempotent
(equal (to-df (to-df x))
(to-df x)))
(defthm to-df-default
(implies (not (rationalp x))
(equal (to-df x) 0)))
(defthm to-df-monotonicity
; See comments in constrained-to-df-monotonicity.
(implies (and (<= x y)
(rationalp x)
(rationalp y))
(<= (to-df x) (to-df y)))
:rule-classes (:linear :rewrite))
(defun dfp (x)
; We need the #-acl2-loop-only case below. For example, without it, then after
; we evaluate (defun f (x) (declare (type double-float x)) (dfp x)) we'll see
; that (f (to-df 3)) evaluates (wrongly) to nil. See the comment in
; translate11-call-1 about allowing dfp to be applied to a :df; that's why we
; need the #-acl2-loop-only case below.
(declare (xargs :guard t :mode :logic))
#+acl2-loop-only
(and (rationalp x)
(= (to-df x) x))
#-acl2-loop-only
(if (rationalp x)
(= (rational (to-df x)) x)
(typep x 'double-float)))
(defun from-df (x)
(declare (xargs :guard (dfp x) :mode :logic))
#+acl2-loop-only
x
; When executing in raw Lisp, we need to ensure that we return an ordinary
; object here, not a double-float. As usual with ACL2 functions, we are free
; to assume that the guard holds.
#-acl2-loop-only
(rational x))
(defun to-dfp (x)
(declare (xargs :guard (rationalp x) :mode :logic))
(from-df (to-df x)))
(defthm dfp-to-df
(dfp (to-df x)))
(defthm dfp-implies-rationalp
(implies (dfp x)
(rationalp x))
:rule-classes :compound-recognizer)
(defthm dfp-implies-to-df-is-identity
; This theorem is trivial, but worth stating, in analogy to
; to-df-of-df-rationalize. Both are justified by the CL HyperSpec:
; http://www.lispworks.com/documentation/HyperSpec/Body/f_ration.htm
(implies (dfp x)
(equal (to-df x) x))
:rule-classes (:forward-chaining :rewrite))
(in-theory (disable dfp to-df))
(encapsulate () (logic)
; We use a surrounding encapsulate for logic mode, rather than of putting
; (logic) inside the partial-encapsulate, to support redundancy in pass 2.
(partial-encapsulate
(((df-round *) => * :formals (x) :guard (rationalp x)))
nil
; In brief: The function df-round is intended to be the rounding function used
; by Common Lisp to satisfy the IEEE 754 spec, which says that floating-point
; operations are performed by rounding the exact mathematical result. We now
; elaborate.
; The Common Lisp HyperSpec says
; (http://www.lispworks.com/documentation/lw71/CLHS/Body/v_featur.htm) of the
; :ieee-floating-point feature:
; If present, indicates that the implementation purports to conform to the
; requirements of IEEE Standard for Binary Floating-Point Arithmetic.
; Thus, we check in acl2.lisp that :ieee-floating-point is in *features*. The
; IEEE 754 spec is summarized thus in https://en.wikipedia.org/wiki/IEEE_754):
; Unless specified otherwise, the floating-point result of an operation is
; determined by applying the rounding function on the infinitely precise
; (mathematical) result. Such an operation is said to be correctly
; rounded. This requirement is called correct rounding.[19]
; In the IEEE 754-2019 spec (see
; https://doi.org/10.1109%2FIEEESTD.2019.8766229) we find the following.
; Section 2.1.
; correct rounding: This standard’s method of converting an infinitely
; precise result to a floating-point number, as determined by the applicable
; rounding direction. A floating-point number so obtained is said to be
; correctly rounded.
; Section 5.1:
; Unless otherwise specified, each of the computational operations specified
; by this standard that returns a numeric result shall be performed as if it
; first produced an intermediate result correct to infinite precision and
; with unbounded range, and then rounded that intermediate result, if
; necessary, to fit in the destination's format (see Clause 4 and Clause 7).
; With the comments above in mind, we now address the fact that we are in a
; partial-encapsulate, so we need to discuss the implicit axioms and the
; intended (implicit) local witness. Note that although the explicit local
; witness and explicit axioms below suffice for ACL2 to admit this
; partial-encapsulate, they do not meet the logical burden imposed by
; partial-encapsulate. For that, we need to describe the implicit axioms as
; well; moreover, these should suffice to justify results obtained when
; evaluating calls of df-round. Note also that although ACL2 does not evaluate
; df-round directly, it evaluates df-round indirectly when applying df+ or any
; other rational df function. For example, (df+ x y) is defined to be
; (df-round (+ x y)), but we have:
; ACL2 !>(df+ (to-df 1/3) (to-df 2/3))
; #d1.0
; ACL2 !>
; Let WRND be our (implicit) local witness for df. We want to base WRND on the
; rounding function used by the host Lisp. In the future we might tie down
; WRND so that it rounds to nearest even; in that case we might replace this
; partial-encapsulate. with a suitable defun and theorems. We could do this
; because the IEEE Spec (2019 version) defines RoundTiesToEven as one would
; expect, in Section 4.3.1, and Section 4.3.3 says the following.
; The roundTiesToEven rounding-direction attribute shall be the default
; rounding direction for results in binary formats.
; This seems to suggest that any Common Lisp implementation with
; :ieee-floating-point in *features* should use roundTiesToEven.
; However, we are not yet quite convinced by the argument above, nor are we
; ready to formalize round-to-even. So for now, we take a different approach
; to defining WRND, as follows.
; The comments in the partial-encapsulate for constrained-to-df explain how a
; local witness is obtained by using a big case statement based on Lisp
; computations. We define WRND that way too, though this time the computations
; are roundings in support of the application of df+ or any other rational df
; function. For example, evaluation of (df/ #d1.0 #d3.0) leads Common Lisp to
; round the rational 1/3 to a df (at least, conceptually, as per the IEEE
; spec), displayed as #d0.3333333333333333; so our local witness maps 1/3 to
; (the rational number) #d0.3333333333333333. The implicit axioms include
; (equal (df-round 1/3) #d0.3333333333333333) and all other such equalities.
; Moreover, we consider all roundings of (+ 0.0 x) where x is a double-float --
; there are only finitely many -- and note that since that sum is surely x,
; therefore x rounds to itself (see [WRND2] below). So to summmarize, our
; intended (and implicit) local witness for df-round, WRND, is defined as
; follows.
; [WRND1] For any rational df operation performed in the given Lisp
; implementation (past or future) completed by rounding rational r to
; double-float x:
; (WRND r) = (rational x).
; [WRND2] Let r = (rational x) for a double-float x; then
; (WRND r) = r.
; [WRND3] If r is not covered by [WRND1] or [WRND2], then
; (WRND r) = 0.
(local (defun df-round (x)
(constrained-to-df x)))
(defthm rationalp-df-round
; This rule is important, since it supports the use of linear arithmetic for
; df+ and other rational df operations.
(rationalp (df-round x))
:rule-classes :type-prescription)
(defthm dfp-df-round
; Clearly (rationalp (WRND r)) follows by definition of WRND. So by definition
; of dfp, it remains to prove that (equal (to-df (WRND r)) (WRND r)) holds for
; all r. If (WRND r) = 0, then this is immediate by constrained-to-df-0.
; Otherwise, cases [WRND1] and [WRND2] of the definition of WRND tell us that
; if (WRND r) = s for some s that is (rational x) for some double-float x. The
; float-rational identity (see constrained-to-df-idempotent) tells us that
; (float (rational x) 0.0D0) = x; Applying rational to both sides and using s =
; (rational x), we have (rational (float s 0.0D0)) = s. So by [WDF3], we can
; prove (equal (to-df s)) s); that is, (equal (to-df (WRND r)) (WRND r)).
(dfp (df-round r)))
(defthm df-round-is-identity-for-dfp
; Assume (dfp r), i.e., (rationalp r) and (equal (to-df r) r). Then by the
; implicit constraints on constrained-to-df generated by the definition of WDF
; (see constrained-to-df), (rational (float r 0.0D0)) = r. That (WRND r) = r
; follows immediately from [WRND2].
(implies (dfp r)
(equal (df-round r)
r)))
(defthm df-round-monotonicity
; To see why we include this property, consider the following example.
; (implies (and (dfp x1) (dfp y1) (dfp x2) (dfp y2)
; (<= x1 x2) (<= y1 y2))
; (<= (df+ x1 y1) (df+ x2 y2))))
; That formula is provable using this monotonicity property, but is likely not
; provable without it. In fact, the same holds if dfp is replaced in the
; hypotheses by rationalp.
; We believe that this property holds for all rounding modes that might be used
; in Common Lisp implementations.
(implies (and (<= x y)
(rationalp x)
(rationalp y))
(<= (df-round x) (df-round y)))
:rule-classes (:linear :rewrite))
)
)
(defthm df-round-idempotent
; This follows from df-round-is-identity-for-dfp together with dfp-df-round.
(equal (df-round (df-round x))
(df-round x)))
(encapsulate () (logic)
; We do it this way (instead of putting (logic) inside the
; partial-encapsulate), to support redundancy in pass 2.
(partial-encapsulate
(((constrained-df-rationalize *) => * :formals (x) :guard (dfp x)))
(constrained-to-df)
(local (defun constrained-df-rationalize (x)
(if (dfp x) x 0)))
(defthm rationalp-constrained-df-rationalize
(rationalp (constrained-df-rationalize x))
:rule-classes :type-prescription)
(defthm to-df-of-constrained-df-rationalize
; This theorem is justified by the "Notes" in the CL HyperSpec topic:
; http://www.lispworks.com/documentation/HyperSpec/Body/f_ration.htm
(implies (dfp x)
(equal (to-df (constrained-df-rationalize x))
x)))
)
)
#+acl2-loop-only
(defun binary-df+ (x y)
; This function is defined in raw Lisp in float-raw.lisp, and it is given a
; suitable signature by the call of install-df-basic-primitives below.
(declare (xargs :mode :logic
:guard (and (dfp x)
(dfp y))))
(df-round (+ x y)))
#+acl2-loop-only
(defun unary-df- (x)
; This function is defined in raw Lisp in float-raw.lisp, and it is given a
; suitable signature by the call of install-df-basic-primitives below.
(declare (xargs :mode :logic
:guard (dfp x)))
(df-round (- x)))
(defthm dfp-minus
(implies (dfp x)
(dfp (- x)))
:hints (("Goal" :in-theory (enable dfp to-df))))
#+acl2-loop-only
(defun unary-df/ (x)
; This function is defined in raw Lisp in float-raw.lisp, and it is given a
; suitable signature by the call of install-df-basic-primitives below.
(declare (xargs :mode :logic
:guard (and (dfp x)
(not (= x 0)))))
(df-round (/ x)))
#+acl2-loop-only
(defun binary-df* (x y)
; This function is defined in raw Lisp in float-raw.lisp, and it is given a
; suitable signature by the call of install-df-basic-primitives below.
(declare (xargs :mode :logic
:guard (and (dfp x)
(dfp y))))
(df-round (* x y)))
#+acl2-loop-only
(defun binary-df/ (x y)
; This function is defined in raw Lisp in float-raw.lisp, and it is given a
; suitable signature by the call of install-df-basic-primitives below.
(declare (xargs :mode :logic
:guard (and (dfp x)
(dfp y)
; It's a wart that the next conjunct uses = rather than df=. But it's one that
; we believe we can get away with. If not, then may well be feasible to defer
; the definition of binary-df/ until after df= is introduced.
(not (= y 0)))))
(df-round (/ x y)))
(defconst *df-basic-primitives*
; These functions take or return a :DF. We overcome potential boot-strapping
; issues with the form (install-df-basic-primitives state) below. Note that
; the constant *df-primitives* (which is explained in a comment in its defconst
; form) consists of dfp, *df-basic-primitives*, and the function symbols
; introduced by df-function-sigs.
'((from-df (:df) (nil))
(to-df (nil) (:df))
(df-string (:df) (nil))
(binary-df+ (:df :df) (:df))
(binary-df* (:df :df) (:df))
(binary-df/ (:df :df) (:df))
(unary-df- (:df) (:df))
(unary-df/ (:df) (:df))))
(defun install-df-basic-primitives-1 (alist wrld)
; At the top level, alist is *df-basic-primitives*.
(declare (xargs :mode :program))
(cond ((endp alist) wrld)
(t (install-df-basic-primitives-1
(cdr alist)
(let* ((tuple (car alist))
(fn (car tuple))
(stobjs-in (cadr tuple))
(stobjs-out (caddr tuple)))
(putprop fn 'stobjs-in stobjs-in
(putprop fn 'stobjs-out stobjs-out
wrld)))))))
; This development is continued in float-b.lisp, which is processed during the
; boot-strap after set-w is defined in history-management.lisp. We would
; process the two files as a single file after history-management.lisp, except
; that we need dfp to be defined in remove-guard-holders1.