dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.24k stars 1.57k forks source link

Round for doubles should take the number of decimals as argument... #8575

Open DartBot opened 11 years ago

DartBot commented 11 years ago

This issue was originally filed by adrian.avil...@gmail.com


If I have the number 12.346 and I need to round it to only two decimals is not possible, all I have is 12.346.round(), and the result is 12.0. It would be ideal to do 12.346.round(2) so the result can be 12.35.

References:

In C# there is Math.Round(double, decimals)

In Pascal there is RoundTo(double, decimals).

lrhn commented 11 years ago

Removed Type-Defect label. Added Type-Enhancement, Area-Library, Triaged labels.

DartBot commented 9 years ago

This comment was originally written by adrian.avila.mtz...@gmail.com


No plans for fixing this one yet?

Has been a while.

eukreign commented 9 years ago

Looks like Dart already has this but it's named strangely, the docs are vague about the rounding part and it returns a string.

1.119.toStringAsFixed(2)

Results in 1.12.

The docs should mention that this method actually does rounding and not simply truncate. Also, it would make more sense to add this capability to the existing num.round() method instead of introducing this newfangled num.toStringAsFixed() method.

zoechi commented 9 years ago

num.round() returns an int AFAIK and there is no way to specify the number of fraction digits in double. This would need something like the decimal number in core.

eukreign commented 9 years ago

@zoechi num.toStringAsFixed() does exactly what you'd expect from round() in other languages. It's just difficult to discover this fact since the Dart docs for num.toStringAsFixed() don't clearly state that it does rounding for you. I think most people would probably look at the existing num.round() method and see it doesn't support decimals and not bother to look at the very poorly named num.toStringAsFixed() rounding method.

zoechi commented 9 years ago

@eukreign I don't argue against that. I think that it just can't be integrated into num.round() because the return types differ.

AbdullahAlAsad commented 5 years ago

What if I need a double with precision, not a string. i.e . need 1.00 or 2.00 . num = 1 double.parse(num.toStringAsFixed(2)) this will return 1 again. Why do we need toStringAsFixed(n) where we can call .toString on every type. Rather this is more important to have precision that return it's original type.

hasen6 commented 5 years ago

Yes toStringAsFixed is for strings, no use for Maths. We need it for doubles - especially since flutter has it's peculiarities with like 85.0 + 0.1 = 85.1, but add another 0.1 and it equals 85.19999999 or whatever.

lrhn commented 5 years ago

That's not a Flutter peculiarity. It's an IEEE-754 floating point peculiarity which is shared by all languages using normal floating points. The value of 85.0 + 0.1 + 0.1 is not the same double as 85.2. It's not the same because the numerical value 0.1 is not exactly representable as a double value, and the two additions of inexact values causes the error to accumulate to the point where the difference is distinguishable.

Dart chooses to give different double values different toString results. So does, e.g., Java and JavaScript, but not C# and Python.

That's not to say that we can't have a rounding operation which rounds to a specific number of decimal digits (and then to the nearest representable double value).

hasen6 commented 5 years ago

@lrhn

That's not to say that we can't have a rounding operation which rounds to a specific number of decimal digits (and then to the nearest representable double value).

That's exactly what the original question is asking for...not to mention my comment as well.

lrhn commented 5 years ago

Yes. That was an attempt to get back on track, and not discuss toString :smile:

hasen6 commented 5 years ago

@lrhn It was never off track. You're the only who's mentioned toString so far in fact. I think you're confused, everyone is talking about toStringAsFixed because that gives decimal precision. We want the equivalent but for doubles.

YoussefLasheen commented 5 years ago

I have a solution. It's kinda of a hack. Multiply the double by a 100 (Increase if you want more precision) then use roundToDouble() and then divide by 100 again.

double num = 0.59841; double roundedNum = (( num *100).roundToDouble())/100;

hasen6 commented 5 years ago

@YoussefLasheen The original post stated that he wanted to specify the amount of decimal precision with a simple integer (1, 2, 3 etc). I already made this kind of formula like yours as a workaround too but I think also the point also is we want it built into Dart like toStringAsFixed

andreidiaconu commented 4 years ago

Taking @YoussefLasheen's idea a bit further, we can write our own extension methods. Keep in mind that working with doubles is not precise and can give unexpected results. Also, a proper implementation for this should also assert that the number of digits is not higher than a certain number, so pow(10, digits) does not overflow.

import 'dart:math';

extension DoubleRounding on double {
  /// Floors to given number of digits
  ///
  /// 10.2468.floorDigits(1) -> 10.2
  /// 10.2468.floorDigits(2) -> 10.24
  /// 10.2468.floorDigits(3) -> 10.246
  ///
  /// Might give unexpected results due to precision loss: 10.2.floorDigits(5) -> 10.199999999999999
  double floorDigits(int digits) {
    if (digits == 0) {
      return this.floorToDouble();
    } else {
      final divideBy = pow(10, digits);
      return ((this * divideBy).floorToDouble() / divideBy);
    }
  }

  double roundDigits(int digits) {
    if (digits == 0) {
      return this.roundToDouble();
    } else {
      final divideBy = pow(10, digits);
      return ((this * divideBy).roundToDouble() / divideBy);
    }
  }

  double ceilDigits(int digits) {
    if (digits == 0) {
      return this.ceilToDouble();
    } else {
      final divideBy = pow(10, digits);
      return ((this * divideBy).ceilToDouble() / divideBy);
    }
  }
}
lrhn commented 4 years ago

Working with doubles while requiring precision is never easy. They are designed to be lossful. Multiplying by pow(10, x) can lose precision (it increases the number of bits required by roughly 2 per x, which might push a number past the 53 available bits of precision). If you only care about .roundDigits(n) for small ns and small numbers, it's going to be fine. If you end up with something already using 48 bits above the decimal point, asking for 3 digits of precision might give a slightly wrong result.

Example:

  var x = (pow(2,43) - 1) + .0625;
  print(x); // 8796093022207.0625
  print(x.roundDigits(5)); // 8796093022207.063
andreidiaconu commented 4 years ago

@lrhn I agree with everything you say. The thread itself asks for something that doubles are not able to do properly. Even so, there might be cases where the proposed solution is useful.

lrhn commented 4 years ago

The functionality can definitely exist. We have the code which does toString and toStringAsFixed, and it can certainly be adapted to generate a new double which is equal to what toStringAsFixed would have output. I'm not sure it's going to be particularly more efficient than actually doing toStringAsFixed, but obviously it can avoid the string allocation. It's still somewhat worrying to have a function documented as rounding a double to a number of decimal points when most decimal fractions cannot be represented precisely as a double.

We want to avoid doing something which gives a wrong result in some situations. Either you can trust the function, or you can't. It's not enough that it works for small numbers. (Obviously, if we know that it works for small numbers below some threshold, we can use it for small numbers and fall back on toStringAsFixed+parse for large numbers).

mario-neb commented 4 years ago

Tested @andreidiaconu ' s methods for a workaround and it works for me.

jamesderlin commented 3 years ago

IMO, people who care about decimal representations probably should consider using something like package:decimal instead of doubles anyway, and then rounding to a specified number of digits is fairly straightforward. (package:decimal also internally uses BigInt and should avoid problems with overflow.)