Zulip Chat Archive
Stream: new members
Topic: Casting to and from Floats
Caleb Schultz Kisby (Mar 27 2023 at 20:28):
Hi again,
I'd like to give my program Floats, and then maybe convert them to something like Rat or Real that's easier to reason about. I don't mind if the things I prove about Rat or Real aren't actually true of the Float, it's an illusion I'm happy to maintain. (At the end of the day, I'd like to be able to both evaluate my Floats and prove things about numbers that they approximate.)
I've looked around in the Zulip archive, and I've found several threads where others mention using Reals instead of Floats. But I'm having trouble figuring out how to actually cast between them. I've looked in Init.Data.OfScientific, Mathlib.Data.Real.Basic, and Std.Data.Rat.Basic.
Am I just looking in the wrong places? Am I approaching this the wrong way?
Thanks,
Caleb
Tomas Skrivan (Mar 27 2023 at 22:13):
To achieve this I currently take an axiomatic approach. I just create a new type that wraps around Float and state that it is a field. Here is a code that just postulates that such type is commutative ring
import Mathlib.Algebra.Ring.Basic
structure MyReal : Type where
val : Float
def _root_.Int.toFloat : Int → Float
| .ofNat z' => z'.toFloat
| .negSucc z' => -(z'+1).toFloat
instance commRing : CommRing MyReal := by
refine' { natCast := fun n => ⟨n.toFloat⟩
intCast := fun z => ⟨z.toFloat⟩
zero := ⟨(0 : Float)⟩
one := ⟨(1 : Float)⟩
mul := λ x y => ⟨x.1 * y.1⟩
add := λ x y => ⟨x.1 + y.1⟩
neg := λ x => ⟨-x.1⟩
sub := λ x y => ⟨x.1 - y.1⟩
npow := λ n x => ⟨x.1^n.toFloat⟩
nsmul := λ n x => ⟨n.toFloat * x.1⟩
zsmul := λ z x => ⟨z.toFloat * x.1⟩,
.. }
all_goals admit -- Just assume that all the laws are true
instance : OfScientific MyReal := ⟨λ m s e => ⟨(OfScientific.ofScientific m s e)⟩⟩
instance : ToString MyReal := ⟨λ x => x.1.toString⟩
#eval (42.0 : MyReal) + (0.123456 : MyReal)
instance : LE MyReal := ⟨λ x y => x.1 ≤ y.1⟩
instance : LT MyReal := ⟨λ x y => x.1 < y.1⟩
instance : DecidableRel (λ x y : MyReal => x ≤ y) :=
λ x y => if h : x.1 ≤ y.1 then isTrue h else isFalse h
instance : DecidableRel (λ x y : MyReal => x < y) :=
λ x y => if h : x.1 < y.1 then isTrue h else isFalse h
example
: (1.0 : MyReal) + (-1.0 + 0.00000000000000001 : MyReal) ≤ 0.0 := by native_decide
example
: (1.0 : MyReal) + (-1.0 + 0.00000000000000001 : MyReal) > 0.0 := by rw[← add_assoc]; native_decide
The end shows that you can get into trouble once you start playing around with native_decide
and decidable inequalities. Here is a thread discussing this inconsistency.
Tomas Skrivan (Mar 27 2023 at 22:23):
Another approach I'm experimenting with now, is where I provide the compiler with different definitions of operations on reals. This can be done with impelmented_by
attribute.
import Mathlib.Data.Real.Basic
import Mathlib.Data.Complex.Exponential
def MyReal := Real
-- `MyReal` and mathlib's reals `ℝ` are defeq
example : MyReal = ℝ := by rfl
-- To convert from float to real we just perform unsafe cast
-- We have to make sure that any operation on `MyReal` has its implementaion
-- overridden by corresponding implementation with Floats
unsafe def MyReal.toFloat (x : MyReal) : Float := unsafeCast x
unsafe def Float.toMyReal (x : Float) : MyReal := unsafeCast x
namespace MyReal
unsafe def Impl.add (x y : MyReal) : MyReal := (x.toFloat + y.toFloat).toMyReal
@[implemented_by Impl.add]
def add (x y : MyReal) : MyReal := ⟨x.1 + y.1⟩
unsafe def Impl.ofScientific (mantissa : Nat) (exponentSign : Bool) (decimalExponent : Nat) : MyReal :=
(OfScientific.ofScientific mantissa exponentSign decimalExponent : Float).toMyReal
instance : Add MyReal := ⟨add⟩
@[implemented_by Impl.ofScientific]
def ofScientific (mantissa : Nat) (exponentSign : Bool) (decimalExponent : Nat) : MyReal :=
let exp := (10:ℝ)^decimalExponent
if exponentSign then
(mantissa / exp : ℝ)
else
(mantissa * exp : ℝ)
instance : OfScientific MyReal := ⟨ofScientific
⟩
unsafe instance : ToString MyReal := ⟨λ x => x.toFloat.toString⟩
#eval (42.0 : MyReal) + (0.123456 : MyReal)
However, I'm unable to show that MyReal
is a field without doing lots of work.
Caleb Schultz Kisby (Mar 28 2023 at 01:09):
Ah, thank you so much! This is perfect. The inconsistency with native_decide
is fine, and is what I'd expect (since you're evaluating the Float
s). I'll just avoid evaluating Float
in the proofs.
Caleb Schultz Kisby (Mar 28 2023 at 01:10):
Also, I didn't know about native_decide
until now (I'm back to learning Lean again!). I used to use Agda, and so I'm unfamiliar with the tactic language. But things like this make it hard to go back to just terms :-)
Kevin Buzzard (Mar 28 2023 at 06:31):
If you don't want to evaluate the floats then why don't you just use reals instead?
Tomas Skrivan (Mar 28 2023 at 12:55):
Evaluating floats via native_decide
in a proof and running a program with floats are two different things.
Caleb Schultz Kisby (Mar 28 2023 at 16:00):
If I can ask a follow-up question -- is there any official plan for handling this sort of cast/conversion in the Lean4 mathlib? (Again, I'm new to Lean, so I'm not familiar with mathlib development. I'm just curious, and I'd also like to avoid writing code that will look archaic in 5 years!)
Martin Dvořák (Mar 28 2023 at 16:02):
Mathematicians are not interested in floats afaik. Some library other than Mathlib4 might incorporate it tho.
Caleb Schultz Kisby (Mar 28 2023 at 16:03):
Oh whoops, good point. I forgot that Float
is dealt with in Init.Data.Float
and Init.Data.OfScientific
anyway :sweat_smile:
Kevin Buzzard (Mar 28 2023 at 16:12):
For sure mathlib is not interested in defs which make the system inconsistent :-)
Reid Barton (Mar 28 2023 at 17:01):
Applied mathematicians of a certain type are plenty interested in floats. If we come up with some useful way to talk about the relationship between idealized calculations with real numbers and actual algorithms with floats, I think it would be within scope for mathlib. But of course it would have to be done in a way that doesn't involve adding false axioms.
At the moment there isn't any plan to do so though, or even any concrete idea of how to do so really.
Kevin Buzzard (Mar 28 2023 at 18:22):
Presumably the way to do this is to have some kind of interval arithmetic and a map from Float
to that? Did anyone ever write down precise functions lower_bound
and upper_bound
from Float
to Rat
which have some kind of useful meaning?
Eric Wieser (Mar 28 2023 at 20:30):
Do we have docs4#Float.toRat ?
Eric Wieser (Mar 28 2023 at 20:31):
I'm not sure the lower/upper bound is that interesting, it's probably just half-way between the adjacent Floats, perhaps with a different definition for each rounding mode
Eric Wieser (Mar 28 2023 at 20:33):
Is it even possible to prove things about Float
in lean4? It looks like you can't actually access the underlying bits
Mario Carneiro (Mar 29 2023 at 00:32):
you can prove things about concrete Floats using native_decide
(which can see through opaque
and hence is a proper axiomatic extension), but that's not good enough for doing general properties about float representation. I would probably do full IEEE modeling by having a FP.Float
type a la docs4#FP.Float and axiomatize the bijection between them (this may require additional functions in core if necessary to define the bijection)
Eric Wieser (Mar 29 2023 at 00:35):
Why would we need the bijection to be axiomatized? We don't have any such axiomatization for things like Uint64
Mario Carneiro (Mar 29 2023 at 00:46):
because Float
is opaque
Mario Carneiro (Mar 29 2023 at 00:47):
I'm talking under the assumption that the definition of Float
in core does not change
Mario Carneiro (Mar 29 2023 at 00:50):
The type docs4#FloatSpec which gives the properties of the float type itself are not sufficient to establish e.g. its cardinality
Mario Carneiro (Mar 29 2023 at 00:52):
you can introduce the "to" and "from" functions using opaque
, similar to most float ops, but any assertions about those functions would need to be axiom
, for example (Float.add x y).toFP = FP.Float.add x.toFP y.toFP
Mario Carneiro (Mar 29 2023 at 00:54):
It would be possible to have structure Float where ofFP :: toFP : FP.Float
though in core; the toFP
and ofFP
functions would be overridden in this case such that Float
has the ABI of a f32
while FP.Float
is a regular lean inductive containing the "unpacked" components
Eric Wieser (Mar 29 2023 at 02:00):
I think that was the version I was expecting
Eric Wieser (Mar 29 2023 at 02:02):
But maybe that's asking too much of core especially if we want implementations of all the float functions within the theory
Martin Dvořák (Apr 11 2023 at 15:19):
This is a long shot, but...
I'd like to do computation with matrices over approximate complex numbers (where the real part is a Float
and the imaginary part is a Float
as well) and I would prefer not to define everything myself. Does Lean 3 or Lean 4 provide anything useful for my purpose?
Ruben Van de Velde (Apr 11 2023 at 15:19):
I think the short answer is "no" :)
Ruben Van de Velde (Apr 11 2023 at 15:20):
Not sure if there's a longer answer yet
Eric Wieser (Apr 11 2023 at 15:35):
I've argued before that we should have such a construction, since it would absorb docs#gaussian_int
Eric Wieser (Apr 11 2023 at 15:36):
In the meantime, you could use a clifford_algebra
!
Reid Barton (Apr 11 2023 at 16:20):
You should check out https://github.com/lecopivo/SciLean
Henrik Böving (Apr 12 2023 at 09:21):
Martin Dvořák said:
This is a long shot, but...
I'd like to do computation with matrices over approximate complex numbers (where the real part is a
Float
and the imaginary part is aFloat
as well) and I would prefer not to define everything myself. Does Lean 3 or Lean 4 provide anything useful for my purpose?
If you dont want to prove anything you can just do Array (Array (Prod Float Float))
orr am I overlooking something?
Reid Barton (Apr 12 2023 at 09:24):
I think this is "defining everything myself"
Eric Wieser (Apr 12 2023 at 09:31):
I would imagine that you can use docs4#Matrix even if you can't use docs4#complex. Though for computation you'll want to occasionally un-lazy the matrix by converting to nested arrays and back
Reid Barton (Apr 12 2023 at 09:33):
Probably closer to "always" than "occasionally".
Reid Barton (Apr 12 2023 at 09:34):
The Mul instance needs some well-behavedness of the underlying ring, which you would need to assert (even though it's false).
Reid Barton (Apr 12 2023 at 09:34):
If you don't want to prove theorems then rolling your own seems better in pretty much every respect
Eric Wieser (Apr 12 2023 at 09:37):
Reid Barton said:
The Mul instance needs some well-behavedness of the underlying ring, which you would need to assert (even though it's false).
Ah, right; because we use finset.univ.sum
to collect the terms, but that's only well-defined if addition is associative.
Reid Barton (Apr 12 2023 at 09:38):
Which is unavoidable I think, because it only assumes a fintype
instance on the indexing types
Reid Barton (Apr 12 2023 at 09:44):
Also, some good old-fashioned for loops are likely to be a lot more efficient than finset.univ.sum
, depending on how good Lean is at inlining
Reid Barton (Apr 12 2023 at 09:46):
When the body of the loop is two fetches and an fmadd
, there is a lot of room to make it slow.
Last updated: Dec 20 2023 at 11:08 UTC