Skip to content

toJSON truncates floats #5733

Open
Open

Description

@pennae

Describe the bug

toJSON unnecessarily truncates floats to six significant figures.

Steps To Reproduce

nix-repl> builtins.toJSON 1.000001
"1"

nix-repl> builtins.toJSON 1048576.1
"1.04858e+06"

Expected behavior

nix-repl> builtins.toJSON 1.000001
"1.000001"

nix-repl> builtins.toJSON 1048576.1
"1048576.1"

nix-env --version output

tested 2.4 and 2.5pre20211126_55275fc

Additional context

found while investigating NixOS/nixpkgs#148824

Activity

  1. mopleen commented on Dec 7, 2021

    @mopleen
    Contributor

    This is the place https://github.com/NixOS/nix/blob/master/src/libutil/json.cc#L34
    Could be fixed like this str << std::setprecision(9) << n; but maybe it's better to use fmt or boost formatting to output floating point numbers 🤔

  2. pennae commented on Mar 11, 2022

    @pennae
    ContributorAuthor

    this also affect toString in a different way. toString always produces six fractional digits, rounding small floats like 1e-9 to 0.

  3. flokli commented on Jan 25, 2023

    @flokli
    Member

    This can't be changed, because it would change hashes across Nix versions.

  4. srhb commented on Apr 25, 2023

    @srhb
    Contributor

    According to my bisect, this was "fixed" in #7313 despite the hash changes. We had some scary math issues as a result.

  5. roberth commented on Apr 30, 2023

    @roberth
    Member

    This can't be changed, because it would change hashes across Nix versions.

    I'd like a more configurable toJSON2 primop that allows the precision to be extended, or optionally disables the collapsing of "outPath" fields.

  6. flokli commented on Apr 30, 2023

    @flokli
    Member

    Displaying floats in decimal is apparently pretty complicated science, and there's multiple competing algorithms to come up with the representation, and even more ways to format it.

    It's not entirely well-defined how Nix does it either. If you're curious, check the commit message of https://cs.tvl.fyi/depot/-/commit/1facd889bba724cf20ea14422ee1e57440b3e761, which describes the challenges while trying to mimic Nix as much as possible.

    I personally think supporting floats to be printed as strings (and by this, allowing that representation to be leaked into output hashing) was a mistake.

    I don't think we should add more builtins treating this differently, or allowing to configure the behavior, but rather discourage the usage of floats in output alltogether. We can't remove it from the language, (even though nixpkgs uses it very little), but we at least could print warnings whenever someone converts a float to string, so they know they produce output that might differ across implementations / architectures / compiler versions.

  7. roberth commented on Apr 30, 2023

    @roberth
    Member

    I agree.
    Until we explicitly formalize and test the semantics of non-integral numbers in the Nix language, the current implementation ("whatever some platform code does") should be avoided.
    Such a project should also include the removal of native floating point operations

    Nixpkgs/NixOS could sidestep the Nix floats by postprocessing something like mkFloat = value: { _type = "float"; inherit value;} etc. It already postprocesses the toJSON output in a derivation (in pkgs.formats), so that seems within reason.
    That also solves the precision limitations, as long as users don't need to compute with floats.

  8. andersk commented on Sep 10, 2023

    @andersk
    Contributor

    Some prior art for deterministically specifying float ↔ string conversions can be found in the ECMAScript specification: Number::toString, StringNumericValue. For historical reasons, that specification has two points where an implementation-defined choice can be made:

    • In Number::toString step 5, the implementation may choose between multiple representations of the shortest length that all round-trip to the input. A deterministic specification of the best choice is provided in Note 2.

    • In the RoundMVResult helper for StringNumericValue, the implementation may round excessively-precise inputs up or down to 20 significant digits. A deterministic specification would omit this allowance and use the input as provided.

    The Ryū, Grisu-Exact, and Dragonbox algorithms all satisfy this stricter deterministic specification; their difference is in performance and not observable rounding behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions