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 JsonNumbers 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):

docs4#Float.frExp

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