Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

num.toStringAsFixed() can return '-0.000' #42472

Open
max-bowden opened this issue Jun 24, 2020 · 9 comments
Open

num.toStringAsFixed() can return '-0.000' #42472

max-bowden opened this issue Jun 24, 2020 · 9 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. enhancement-breaking-change An enhancement which is breaking. library-core type-enhancement A request for a change that isn't a bug

Comments

@max-bowden
Copy link

Version
Dart VM version: 2.8.4 (stable) (Wed Jun 3 12:26:04 2020 +0200) on "macos_x64"

Description
Calling num.toStringAsFixed() can return "-0.0000". Surely we never want to format a string as minus 0?

Steps to reproduce
num.parse("-1.4921397450962104e-13").toStringAsFixed(4)

@kevmoo kevmoo added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core labels Jun 24, 2020
@kevmoo
Copy link
Member

kevmoo commented Jun 24, 2020

Is this a bug, @lrhn ?

@lrhn
Copy link
Member

lrhn commented Jun 25, 2020

Doesn't seem like a bug. There is a difference between 0.0 and -0.0, and toString returns "0.0" and "-0.0" respectively.
It is consistent that asking for a fixed precision of that gives "0.000" and "-0.000".

Whether it's desirable is a different question. We can't compare with JavaScript behavior because their toString returns "0" for -0.0 too.
If we check Java, then:

      double x = -0.0;
      String s = String.format("%.3f", x);
      System.out.println(s); // Prints "-0.000"

or C#

var x = -0.0;
var s = String.Format("{0:0.000}", x);
Console.WriteLine(s); // prints "0.000"

so there isn't consistency.
People do want workarounds for the Java behavior (https://stackoverflow.com/questions/11929096/negative-sign-in-case-of-zero-in-java).

@kevmoo kevmoo closed this as completed Jun 25, 2020
@max-bowden
Copy link
Author

max-bowden commented May 2, 2021

I just want to record the fact that although I can see where @lrhn is coming I do disagree with the conclusion and believe this is a bug and that it should be re-examined.

I appreciate that in the implementation of dart-lang there is a difference between 0.00 and -0.00, however it is a universally agreed fact of all mathematics that 0.00 and -0.00 are identical. I don't think it's right that dart-langs opinion of numbers trumps all known mathematical understanding.

I think @lrhn might be looking at this too much from the PoV of a dart-lang dev and missing the bigger picture - which is completely understandable because it happens to everyone, even the best devs.

C# matches the behaviour I think is expected, Java doesn't and people want workarounds, and JavaScript chops off the trailing 0s, but never the less also drops the '-'. So as far as I can see, all three of these examples point to the correct behaviour being what I have suggested. I'm not sure why you both @lrhn and @kevmoo still came to the conclusion that this shouldn't be resolved.

Writing this off as 'undesirable' and not a 'bug' is just playing with words, in my opinion. Unless there is a really good reason why this can't be fixed (i.e. major effort/rewrite/refactor required) I think it should be resolved. It's going to cause pain for many devs for years to come, and I can't think of a single use-case where it would be desirable.

If I'm missing somthing (i.e. an important use-case where formatting a number as '-0.00' is deisable) then please do enlighten me - I'm completly open to changing my mind!

I hope you can have another look at this. I appreciate your time 👍

@mraleph
Copy link
Member

mraleph commented May 3, 2021

It seems that a lot of the languages are preserving the sign when formatting minus zero, see the table: rust-lang/rfcs#1074 (comment)

Dart is by no means unique here.

I appreciate that in the implementation of dart-lang there is a difference between 0.00 and -0.00, however it is a universally agreed fact of all mathematics that 0.00 and -0.00 are identical.

It's not really Dart specific - this is just how floating point numbers work. Note that floating point numbers don't behave like mathematical real numbers, despite being called real in some programming languages. 0.0 and -0.0 are just one of many quirks of these numbers: 0.0 and -0.0 are equal but not by any means identical (in fact identical(-0.0, 0.0) is false in Dart). One way to observe the difference is to divide 1 by zero: you get positive infinity when dividing by 0.0 and negative infinity when dividing by -0.0.

@max-bowden
Copy link
Author

Thanks for explaining this @mraleph. I think the issue is that I assumed toStringAsFixed() should provided a real representation but in actuality the docs do not say that. I now understand that many languages don't format floating point numbers as real by default, so I can see how the current behaviour is justifiable.

Going forward I think it would really nice if:

  1. the toStringAsFixed() doc was explicit on this (for uninformed people like me :D)
  2. there was a way to format nums as real - perhaps toStringAsFixed(real: true) ?

Do you have a view on this? Should I create new issues for these things? I've read the CONTRIBUTING.md but would still like some guidance before I do anything.

Cheers

@lrhn
Copy link
Member

lrhn commented May 4, 2021

The real question here is which use-cases toStringAsFixed is intended for.

It's not for round-tripping double values through double.parse because it deliberately drops precision. That's what toString should be used for, d == double.parse(d.toString()) should generally be true.

It's not for rounding (by round-tripping through double.parse). That's a use, but only because we don't provide a better general round to round to a specific number of decimal digits without going through as string. Not an intended use.

So, it's most likely for displaying, and in that case, the distinction between -0.0 and 0.0 is likely not important.
Removing the - could be argued to be reasonable for that use-case.

The next question is then whether anyone would be broken by actually changing it. Most likely not, displaying a -0.0 is rare.
(The workaround is very simple, just add 0.0 to the value before converting, since -0.0 + 0.0 is 0.0).

@max-bowden
Copy link
Author

max-bowden commented May 6, 2021

That was my initial impression also - that toStringAsFixed() was intended and used for display purposes.

So based on this new information it sounds like it would be reasonable and useful to remove the - form -0.00 returned by toStringAsFixed?

If we are worried about it being a breaking change then could we include it in a more major release? But I do agree with @lrhn that it will probably not be break anything of any significance. I'm sure it will break someone's wacky setup somewhere (i.e. some crazy debug workflow where they log numbers using toStringAsFixed and for some reason need to see the - on -0.00), but that is true for basically everything including bug fixes, so I don't think we should let that stop us.

Is there anything I can do to help further the issue?

Cheers

@Ae-Mc
Copy link

Ae-Mc commented Nov 7, 2021

Based on final conclusion this issue should be reopened.

@lrhn lrhn reopened this Nov 8, 2021
@lrhn lrhn added enhancement-breaking-change An enhancement which is breaking. type-enhancement A request for a change that isn't a bug labels Nov 8, 2021
@rakudrama
Copy link
Member

I am not in favor of changing toStringAsFixed.

The phrase 'for display purposes' is underspecified, since it tells us nothing about the audience.

For a technical audience (historically the audience for floating-point calculations) you probably want a negative sign when the value is negative but rounded to zero to fit the specified precision.
(-0.001).toStringAsFixed(2) --> -0.00 gives a hint that the value is small, but negative, for example, you can expect ice formation, the current is flowing in the wrong direction, or the point is outside of your property.
IEEE negative-zero is just an extreme case of this.

Any change to toStringAsFixed would make it a better fit for one audience at the expense of another.
Rather than deprecate an audience, I would prefer to add a new method that satisfies the different common audience.
A new method would be less of a breaking change, and if the new behaviour was found lacking in some way, we could break the new feature without breaking all the users of the old feature again.

A new method can be implemented as a static extension method.

I wanted to say a better approach for 'display purposes' its to embrace the audience and provide output customized for the audience. NumberFormat provides more control, including deeper localization, such as the decimal symbol and digit grouping. However, I have failed to get NumberFormat to do what the OP requested - dropping the minus indication exactly when the value rounds to zero.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. enhancement-breaking-change An enhancement which is breaking. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

6 participants