Skip to content

Commit 87843ad

Browse files
authored
feat: improvements to the floating-point API (#14481)
This PR improves the API surrounding `Float` / `Float.Model` / `Float32` / `Float32.Model` / `UnpackedFloat` in the following ways: - The declarations `Float.nan` / `Float.inf` / `Float32.nan` / `Float32.inf` and their corresponding models `Float.Model.nan` / `Float.Model.inf` / `Float32.Model.nan` / `Float32.Model.inf` are added (upstreamed from batteries, if you will). - The abbreviations `Int.toFloat` and `Int.toFloat32` are added, analogous to the existing `Nat.toFloat` and `Nat.toFloat32`. - `Float.Model.Format` now requires `2 ≤ exponentBits` instead of just `0 < exponentBits`; which is a necessary condition for `pack` and `unpack` to behave correctly - The definitions `Float.ofNat` / `Float.ofInt` / `Float32.ofNat` / `Float32.ofInt` are now exposed. - The type `Float.Model.UnpackedFloat.Sign` now has `deriving DecidableEq` instead of just `deriving BEq`. - The definitions for `unpackMantissa` / `unpackExponent` / `unpackSign` now use `BitVec.extractLsb'` instead of `BitVec.extractLsb`
1 parent 0e8a9eb commit 87843ad

12 files changed

Lines changed: 99 additions & 17 deletions

File tree

src/Init/Data/Float/Float.lean

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ deriving instance DecidableEq for Float
4848
instance : Nonempty Float :=
4949
⟨⟨default⟩⟩
5050

51+
/--
52+
The special floating-point value `NaN`, short for “not a number”. This value comes up as the result
53+
of erroneous computation like `0 / 0`.
54+
55+
Comparing any value with `NaN` using `==`, `<`, `≤` results in `false`, including `nan == nan`
56+
and `nan ≤ nan`. However, comparing with `NaN` using propositional equality will result in `true`
57+
if and only if the other value is also `NaN`, which means that `nan = nan` is true.
58+
59+
Note: While there are many bit patterns that represent a “not a number” value, they are all equal
60+
in the logical model of `Float`, that is, all floating-point values `f : Float` with `f.isNaN`
61+
are propositionally equal to `nan`.
62+
-/
63+
def Float.nan : Float := .ofModel .nan
64+
65+
/--
66+
The special floating-point value `+Inf`, short for positive infinity, used as a result for
67+
overflowing computations like `1 / 0`.
68+
-/
69+
def Float.inf : Float := .ofModel .inf
70+
5171
/--
5272
Adds two 64-bit floating-point numbers according to IEEE 754. Typically used via the `+` operator.
5373
@@ -229,10 +249,12 @@ This function has a logical model in terms of `Float.Model`.
229249
fun a => a.toModel.toUSize
230250

231251
/--
232-
Checks whether a floating point number is `NaN` (“not a number”) value.
252+
Checks whether a floating point number is a `NaN` (“not a number”) value.
233253
234254
`NaN` values result from operations that might otherwise be errors, such as dividing zero by zero.
235255
256+
This function returns `true` if and only if the input is propositionally equal to `Float.nan`.
257+
236258
This function has a logical model in terms of `Float.Model`. It is compiled to the C operator `isnan`.
237259
-/
238260
@[extern "lean_float_isnan"] def Float.isNaN : Float → Bool :=

src/Init/Data/Float/Float32.lean

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ deriving instance DecidableEq for Float32
4848
instance : Nonempty Float32 :=
4949
⟨⟨default⟩⟩
5050

51+
/--
52+
The special floating-point value `NaN`, short for “not a number”. This value comes up as the result
53+
of erroneous computation like `0 / 0`.
54+
55+
Comparing any value with `NaN` using `==`, `<`, `≤` results in `false`, including `nan == nan`
56+
and `nan ≤ nan`. However, comparing with `NaN` using propositional equality will result in `true`
57+
if and only if the other value is also `NaN`, which means that `nan = nan` is true.
58+
59+
Note: While there are many bit patterns that represent a “not a number” value, they are all equal
60+
in the logical model of `Float32`, that is, all floating-point values `f : Float32` with `f.isNaN`
61+
are propositionally equal to `nan`.
62+
-/
63+
def Float32.nan : Float32 := .ofModel .nan
64+
65+
/--
66+
The special floating-point value `+Inf`, short for positive infinity, used as a result for
67+
overflowing computations like `1 / 0`.
68+
-/
69+
def Float32.inf : Float32 := .ofModel .inf
70+
5171
/--
5272
Adds two 32-bit floating-point numbers according to IEEE 754. Typically used via the `+` operator.
5373
@@ -226,10 +246,12 @@ This function has a logical model in terms of `Float32.Model`.
226246
fun a => a.toModel.toUSize
227247

228248
/--
229-
Checks whether a floating point number is `NaN` ("not a number") value.
249+
Checks whether a floating point number is a `NaN` ("not a number") value.
230250
231251
`NaN` values result from operations that might otherwise be errors, such as dividing zero by zero.
232252
253+
This function returns `true` if and only if the input is propositionally equal to `Float32.nan`.
254+
233255
This function has a logical model in terms of `Float32.Model`. It is compiled to the C operator `isnan`.
234256
-/
235257
@[extern "lean_float32_isnan"] def Float32.isNaN : Float32 → Bool :=

src/Init/Data/Float/Model/Float.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ def pack (f : UnpackedFloat) : Float.Model where
5353
toBits := UInt64.ofBitVec (UnpackedFloat.pack Format.binary64 f)
5454
valid := by simp
5555

56+
/--
57+
The special `NaN` value.
58+
-/
59+
def nan : Float.Model :=
60+
pack .notANumber
61+
62+
/--
63+
The special `Inf` value.
64+
-/
65+
def inf : Float.Model :=
66+
pack (.infinity .positive)
67+
5668
/--
5769
Compute the sum of two `Float.Model`.
5870
-/

src/Init/Data/Float/Model/Float32.lean

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ def pack (f : UnpackedFloat) : Float32.Model where
5656
toBits := UInt32.ofBitVec (UnpackedFloat.pack Format.binary32 f)
5757
valid := by simp
5858

59+
/--
60+
The special `NaN` value.
61+
-/
62+
def nan : Float32.Model :=
63+
pack .notANumber
64+
65+
/--
66+
The special `Inf` value.
67+
-/
68+
def inf : Float32.Model :=
69+
pack (.infinity .positive)
70+
5971
/--
6072
Compute the sum of two `Float32.Model`.
6173
-/

src/Init/Data/Float/Model/Format/Basic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ structure Format where
3333
hm : 0 < mantissaBitsWithoutImplicit := by decide
3434
/-- The number of bits in the exponent. -/
3535
exponentBits : Nat
36-
he : 0 < exponentBits := by decide
36+
he : 2 exponentBits := by decide
3737

3838
namespace Format
3939

src/Init/Data/Float/Model/Unpacked/Pack/Basic.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,21 @@ will be missing the implicit bit.
6666
-/
6767
def unpackMantissa {spec : Format} (b : BitVec spec.numBits) :
6868
BitVec spec.mantissaBitsWithoutImplicit :=
69-
b.extractLsb (spec.mantissaBitsWithoutImplicit - 1) 0 |>.cast (by have := spec.hm; omega)
69+
b.extractLsb' 0 spec.mantissaBitsWithoutImplicit
7070

7171
/--
7272
Unpacks the exponent portion of the packed float.
7373
-/
7474
def unpackExponent {spec : Format} (b : BitVec spec.numBits) :
7575
BitVec spec.exponentBits :=
76-
b.extractLsb (spec.mantissaBitsWithoutImplicit + spec.exponentBits - 1) spec.mantissaBitsWithoutImplicit |>.cast (by have := spec.he; omega)
76+
b.extractLsb' spec.mantissaBitsWithoutImplicit spec.exponentBits
7777

7878
/--
7979
Unpacks the sign bit of the packed float.
8080
-/
8181
def unpackSign {spec : Format} (b : BitVec spec.numBits) :
8282
BitVec 1 :=
83-
b.extractLsb (spec.mantissaBitsWithoutImplicit + spec.exponentBits) (spec.mantissaBitsWithoutImplicit + spec.exponentBits) |>.cast (by omega)
83+
b.extractLsb' (spec.mantissaBitsWithoutImplicit + spec.exponentBits) 1
8484

8585
/--
8686
Unpacks the given float according to the given specification.

src/Init/Data/Float/Model/Unpacked/Pack/Lemmas.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,6 @@ theorem valid_pack {spec : Format} {f : UnpackedFloat} : spec.Valid (pack spec f
4949
omega
5050
omega
5151
| case6 s m e hm actualMantissaBits biasedExponent h₁ h₂ =>
52-
simp [Nat.pos_iff_ne_zero.1 spec.he]
52+
simp [Nat.ne_zero_of_lt spec.he]
5353

5454
end Float.Model.UnpackedFloat

src/Init/Data/Float/Model/Unpacked/Sign.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ inductive Sign where
2727
| negative : Sign
2828
/-- Positive (`+`) sign. -/
2929
| positive : Sign
30-
deriving Repr, BEq
30+
deriving Repr, DecidableEq
3131

3232
namespace Sign
3333

src/Init/Data/OfScientific.lean

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,26 @@ instance : OfScientific Float where
6464
Converts a natural number into the closest-possible 64-bit floating-point number, or an infinite
6565
floating-point value if the range of `Float` is exceeded.
6666
-/
67-
@[export lean_float_of_nat]
68-
def Float.ofNat (n : Nat) : Float :=
67+
@[export lean_float_of_nat, expose]
68+
protected def Float.ofNat (n : Nat) : Float :=
6969
OfScientific.ofScientific n false 0
7070

7171
/--
7272
Converts an integer into the closest-possible 64-bit floating-point number, or positive or negative
7373
infinite floating-point value if the range of `Float` is exceeded.
7474
-/
75-
def Float.ofInt : Int → Float
75+
@[expose] protected def Float.ofInt : Int → Float
7676
| Int.ofNat n => Float.ofNat n
7777
| Int.negSucc n => Float.neg (Float.ofNat (Nat.succ n))
7878

79-
instance : OfNat Float n := ⟨Float.ofNat n⟩
79+
instance : OfNat Float n := ⟨Float.ofNat n⟩
8080

8181
@[inherit_doc Float.ofNat] abbrev Nat.toFloat (n : Nat) : Float :=
8282
Float.ofNat n
8383

84+
@[inherit_doc Float.ofInt] abbrev Int.toFloat (n : Int) : Float :=
85+
Float.ofInt n
86+
8487
/--
8588
Precomputed values of `10 ^ e` for `0 ≤ e ≤ 10 `. In this range, the values can be represented
8689
exactly.
@@ -91,7 +94,7 @@ exactly.
9194
Float32.ofBits 0x49742400, Float32.ofBits 0x4B189680, Float32.ofBits 0x4CBEBC20,
9295
Float32.ofBits 0x4E6E6B28, Float32.ofBits 0x501502F9]
9396

94-
@[expose] protected def Float32.ofScientific (m :Nat) (s : Bool) (e : Nat) : Float32 :=
97+
@[expose] protected def Float32.ofScientific (m : Nat) (s : Bool) (e : Nat) : Float32 :=
9598
-- See comments on `Float.ofScientific`.
9699
if h : m < 2 ^ 23 ∧ e ≤ 10 then
97100
let powerOfTen : Float32 :=
@@ -111,19 +114,22 @@ instance : OfScientific Float32 where
111114
Converts a natural number into the closest-possible 32-bit floating-point number, or an infinite
112115
floating-point value if the range of `Float32` is exceeded.
113116
-/
114-
@[export lean_float32_of_nat]
115-
def Float32.ofNat (n : Nat) : Float32 :=
117+
@[export lean_float32_of_nat, expose]
118+
protected def Float32.ofNat (n : Nat) : Float32 :=
116119
OfScientific.ofScientific n false 0
117120

118121
/--
119122
Converts an integer into the closest-possible 32-bit floating-point number, or positive or negative
120123
infinite floating-point value if the range of `Float32` is exceeded.
121124
-/
122-
def Float32.ofInt : Int → Float32
125+
@[expose] protected def Float32.ofInt : Int → Float32
123126
| Int.ofNat n => Float32.ofNat n
124127
| Int.negSucc n => Float32.neg (Float32.ofNat (Nat.succ n))
125128

126-
instance : OfNat Float32 n := ⟨Float32.ofNat n⟩
129+
instance : OfNat Float32 n := ⟨Float32.ofNat n⟩
127130

128131
@[inherit_doc Float32.ofNat] abbrev Nat.toFloat32 (n : Nat) : Float32 :=
129132
Float32.ofNat n
133+
134+
@[inherit_doc Float32.ofInt] abbrev Int.toFloat32 (n : Int) : Float32 :=
135+
Float32.ofInt n

tests/compile/float.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def tst1 : IO Unit := do
2121
IO.println (0 / 0 : Float).toUInt32
2222
IO.println (0 / 0 : Float).toUInt64
2323
IO.println (0 / 0 : Float).toUSize
24+
IO.println Float.nan.isNaN
25+
IO.println Float.inf.isInf
2426

2527
IO.println (-1 : Float).toUInt8
2628
IO.println (256 : Float).toUInt8

0 commit comments

Comments
 (0)