Skip to content

Commit a2f9553

Browse files
committed
Fix bugs in floating point optimizations
1. Fix type of (/ 0.0): Changed from (-> -NonPosReal -NonPosReal) to (-> -NegReal -NonPosReal) to correctly handle (/ (min 0.0 0)). 2. Avoid optimizing float exprs when conversion can change result: Added safe-to-convert? check to prevent converting large exact numbers to infinity before operations. 3. Fix float-complex multiplication crash with exact integer operands: The imaginary part calculation was using unsafe-fl* with exact integers when non-float optimization preserved exact arithmetic. Based on PR #1381. Fixes #1042.
1 parent 7179772 commit a2f9553

File tree

4 files changed

+60
-9
lines changed

4 files changed

+60
-9
lines changed

typed-racket-lib/typed-racket/base-env/base-env-numeric.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1249,7 +1249,7 @@
12491249
(varop-1+ -InexactReal)
12501250
;; reals
12511251
(varop-1+ -PosReal -NonNegReal)
1252-
(-> -NonPosReal -NonPosReal)
1252+
(-> -NegReal -NonPosReal)
12531253
(-> -NegReal -NegReal -NonNegReal) ; 0.0 is non-neg, but doesn't preserve sign
12541254
(-> -NegReal -PosReal -NonPosReal) ; idem
12551255
(-> -PosReal -NegReal -NonPosReal) ; idem

typed-racket-lib/typed-racket/optimizer/float-complex.rkt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,16 +208,21 @@
208208
(define new-imag-id (if both-real?
209209
(mark-as-real (car is))
210210
(car is)))
211+
;; Helper to convert non-float values to flonum for unsafe ops
212+
(define (maybe-to-float v)
213+
(if (as-non-float v)
214+
#`(real->double-flonum #,v)
215+
v))
211216
(loop (car rs) new-imag-id (cdr e1) (cdr e2) (cdr rs) (cdr is)
212217
;; complex multiplication, imag part, then real part (reverse)
213218
;; we eliminate operations on the imaginary parts of reals
214219
(list* #`((#,new-imag-id)
215220
#,(cond ((and o-real? e-real?) #'0.0)
216-
(o-real? #`(unsafe-fl* #,o1 #,(car e2)))
217-
(e-real? #`(unsafe-fl* #,o2 #,(car e1)))
221+
(o-real? #`(unsafe-fl* #,(maybe-to-float o1) #,(car e2)))
222+
(e-real? #`(unsafe-fl* #,o2 #,(maybe-to-float (car e1))))
218223
(else
219-
#`(unsafe-fl+ (unsafe-fl* #,o2 #,(car e1))
220-
(unsafe-fl* #,o1 #,(car e2))))))
224+
#`(unsafe-fl+ (unsafe-fl* #,o2 #,(maybe-to-float (car e1)))
225+
(unsafe-fl* #,(maybe-to-float o1) #,(car e2))))))
221226
#`((#,(car rs))
222227
#,(cond [(and o-nf e-nf both-real?)
223228
;; we haven't seen float operands yet, so

typed-racket-lib/typed-racket/optimizer/float.rkt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,31 @@
112112
(not (subtypeof? stx -SingleFlonum))
113113
(not (subtypeof? stx -Int))))
114114

115+
;; Check if an expression is a literal infinity
116+
(define (literal-infinity? a)
117+
(syntax-parse a #:literal-sets (kernel-literals)
118+
[(#%expression e) (literal-infinity? #'e)]
119+
[(quote n) #:when (and (flonum? (syntax-e #'n))
120+
(not (rational? (syntax-e #'n))))
121+
#t]
122+
[_ #f]))
123+
124+
;; Check whether an expression can be safely converted to flonum without
125+
;; changing the result (e.g., a large finite number becoming infinity).
126+
;; When there's an infinity operand, converting a huge exact number first
127+
;; can change semantics (e.g., huge - inf = -inf, but +inf - inf = nan).
128+
(define (safe-to-convert? a has-infinity-operand?)
129+
(or (subtypeof? a -Fixnum)
130+
(syntax-parse a #:literal-sets (kernel-literals)
131+
[(#%expression e) (safe-to-convert? #'e has-infinity-operand?)]
132+
;; Quoted flonum - already a float, safe
133+
[(quote n) #:when (flonum? (syntax-e #'n)) #t]
134+
;; Quoted real number - check if it converts to a finite float
135+
[(quote n) #:when (real? (syntax-e #'n))
136+
(rational? (real->double-flonum (syntax-e #'n)))]
137+
;; Non-literal expressions: if there's an infinity operand, be conservative
138+
;; because we can't prove the value won't overflow
139+
[_ (not has-infinity-operand?)])))
115140

116141
(define-syntax-class float-opt-expr
117142
#:commit
@@ -129,7 +154,11 @@
129154
;; for now, accept anything that can be coerced to float
130155
;; finer-grained checking is done below
131156
(~between fs:float-arg-expr 2 +inf.0) ...)
132-
#:when (let* ([safe-to-opt?
157+
#:when (let* ([;; Check if any operand is a literal infinity
158+
has-infinity-operand?
159+
(for/or ([a (in-syntax #'(fs ...))])
160+
(literal-infinity? a))]
161+
[safe-to-opt?
133162
;; For it to be safe, we need:
134163
;; - the result to be a float, in which case coercing args to floats
135164
;; won't change the result type
@@ -141,11 +170,16 @@
141170
;; operations into float operations by accident.
142171
;; (Note: could allow for more args, if not next to each other, but
143172
;; probably not worth the trouble (most ops have 2 args anyway))
173+
;; - when there's an infinity operand, be more careful about
174+
;; converting exact numbers that might overflow
144175
(and (subtypeof? this-syntax -Flonum)
145176
(for/and ([a (in-syntax #'(fs ...))])
146177
;; flonum or provably non-zero
178+
;; also need to make sure that coercing to float won't change
179+
;; a large finite number to infinity, altering the result
147180
(or (subtypeof? a -Flonum)
148-
(subtypeof? a (Un -PosReal -NegReal))))
181+
(and (subtypeof? a (Un -PosReal -NegReal))
182+
(safe-to-convert? a has-infinity-operand?))))
149183
(>= 1
150184
(for/sum ([a (in-syntax #'(fs ...))]
151185
#:when (not (subtypeof? a -Flonum)))

typed-racket-test/optimizer/known-bugs.rkt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
(define (mk-eval lang)
2020
(call-with-trusted-sandbox-configuration
2121
(λ ()
22-
(parameterize ([sandbox-memory-limit 300])
22+
(parameterize ([sandbox-memory-limit 3000])
2323
(make-evaluator lang)))))
2424
(define racket-eval (mk-eval 'racket))
2525
(define tr-eval (mk-eval 'typed/racket))
@@ -85,6 +85,9 @@
8585
;; Multiplication of multiple args should keep exact semantics for exact args
8686
(good-opt (* (expt 10 500) (expt 10 -500) 1.0+1.0i))
8787

88+
;; Multiplication with multiple exact reals and a float complex should not crash
89+
(good-opt (* 2 3 1.0+1.0i))
90+
8891
;; Addition of multiple args should keep exact semantics for exact args
8992
(good-opt (+ (expt 10 501) (expt -10 501) 1.0+1.0i))
9093

@@ -99,7 +102,16 @@
99102
(good-opt (conjugate 0.0+0.0i))
100103

101104
;; Magnitude should always return positive results
102-
(good-opt (magnitude -1.0-2i))))
105+
(good-opt (magnitude -1.0-2i))
106+
107+
;; Division of 0.0 should return correct sign
108+
(good-opt (/ (min 0.0 0)))
109+
110+
;; Subtraction should not convert large numbers to infinity prematurely
111+
(good-opt (- (expt 10 309) +inf.0))
112+
113+
;; make-polar with NaN should return complex NaN, not real NaN
114+
(good-opt (+ 1.0 (make-polar +nan.0 1.0)))))
103115

104116
(module+ main
105117
(require rackunit/text-ui)

0 commit comments

Comments
 (0)