Zulip Chat Archive
Stream: lean4
Topic: Floats vs JsonNumber
Adam Topaz (Jun 01 2023 at 21:09):
I'm planning on doing some (very mild) computations with some floats that will be fetched from some external API.
These numbers are parsed as JsonNumber
s but it seems that this type doesn't support any arithmetic operations.
If I convert this JsonNumber
to a float and back, it seems to lose some information. E.g.:
def bar : JsonNumber := 0.12398711
#eval bar
-- ⟨12398711,8⟩
#eval JsonNumber.fromFloat? <$> ((fromJson? <| .num bar) : Except String Float)
-- Except.ok (Sum.inr ⟨123987,6⟩)
Is there some way to make this operation lossless?
Adam Topaz (Jun 01 2023 at 21:10):
Or a more general question: What's the preferred way to calculate with numbers from a Json
object?
Adam Topaz (Jun 01 2023 at 21:20):
Adam Topaz said:
Is there some way to make this operation lossless?
Of course the answer is no, based on how JsonNumber
is defined.
Adam Topaz (Jun 01 2023 at 21:23):
Perhaps more to the point: has someone implemented double precision floats somewhere?
Mac Malone (Jun 01 2023 at 21:24):
Adam Topaz said:
Perhaps more to the point: has someone implemented double precision floats somewhere?
Float
is double
Adam Topaz (Jun 01 2023 at 21:25):
Ok now I'm confused.
Adam Topaz (Jun 01 2023 at 21:25):
Why then is my number in the above example truncated to 6 decimal places?
Gabriel Ebner (Jun 02 2023 at 00:29):
Obviously because toString
truncates to 6 decimal places:
/-- Creates a json number from a positive float, panicking if it isn't.
[todo]EdAyers: Currently goes via a string representation, when more float primitives are available
should switch to using those. -/
private def fromPositiveFloat! (x : Float) : JsonNumber :=
match Lean.Syntax.decodeScientificLitVal? (toString x) with
| none => panic! s!"Failed to parse {toString x}"
| some (m, sign, e) => OfScientific.ofScientific m sign e
Mac Malone (Jun 02 2023 at 00:31):
Also note that this can be observed from just the following:
#eval bar.toFloat
-- 0.123987
Mac Malone (Jun 02 2023 at 00:32):
To see that toString
rounds to 6 decimal places, observe:
#eval bar.toFloat * 100
-- 12.398711
Adam Topaz (Jun 02 2023 at 00:51):
Ah ok I see
Adam Topaz (Jun 02 2023 at 00:51):
I missed that comment :)
Adam Topaz (Jun 02 2023 at 00:54):
But this is a problem isn’t it? Any time we make a JSON
from something with floats the numbers will get truncated.
Adam Topaz (Jun 02 2023 at 00:56):
Or is there some separate ToJson
instance for floats?
Jason Rute (Jun 02 2023 at 00:58):
I guess the question is if “when more float primatives are available”, is now or the future?
Gabriel Ebner (Jun 02 2023 at 03:17):
No the ToJson Float
instance unfortunately uses fromFloat?
.
Gabriel Ebner (Jun 02 2023 at 03:22):
I think this counts as a bug. It's not just number of digits either. Numbers like 1e-15
are truncated as well.
Gabriel Ebner (Jun 02 2023 at 03:26):
If I wanted to add a proper ToJson instance for float using the current API, I'd extract the exponent using (f.abs.log2 + 2048).toUInt32 - 2048
, then get the mantissa using f.scaleB (60 - e) |>.toUInt64
and then piece that together as a JsonNumber.
Mario Carneiro (Jun 02 2023 at 04:22):
I could have sworn I added a primitive for extracting parts of a float
Mario Carneiro (Jun 02 2023 at 04:23):
Gabriel Ebner (Jun 02 2023 at 17:32):
I briefly looked at frExp
, but you still need to extract the mantissa manually (and fiddle with the signs because we don't have Float.toInt64
) so it doesn't help much.
Last updated: Dec 20 2023 at 11:08 UTC