I want to get one decimal place of a double
in Dart
. I use the toStringAsFixed()
method to get it, but it returns a round-up value.
double d1 = 1.151;
double d2 = 1.150;
print('$d1 is ${d1.toStringAsFixed(1)}');
print('$d2 is ${d2.toStringAsFixed(1)}');
Console output:
1.151 is 1.2
1.15 is 1.1
How can I get it without a round-up value? Like 1.1
for 1.151
too. Thanks in advance.
CodePudding user response:
Not rounding seems highly questionable to me1, but if you really want to truncate the string representation without rounding, then I'd take the string representation, find the decimal point, and create the appropriate substring.
There are a few potential pitfalls:
- The value might be so large that its normal string representation is in exponential form. Note that
double.toStringAsFixed
just returns the exponential form anyway for such large numbers, so maybe do the same thing. - The value might be so small that its normal string representation is in exponential form.
double.toStringAsFixed
already handles this, so instead of usingdouble.toString
, usedouble.toStringAsFixed
with the maximum number of fractional digits. - The value might not have a decimal point at all (e.g. NaN, infinity, -infinity). Just return those values as they are.
extension on double {
// Like [toStringAsFixed] but truncates (toward zero) to the specified
// number of fractional digits instead of rounding.
String toStringAsTruncated(int fractionDigits) {
// Require same limits as [toStringAsFixed].
assert(fractionDigits >= 0);
assert(fractionDigits <= 20);
if (fractionDigits == 0) {
return truncateToDouble().toString();
}
// [toString] will represent very small numbers in exponential form.
// Instead use [toStringAsFixed] with the maximum number of fractional
// digits.
var s = toStringAsFixed(20);
// [toStringAsFixed] will still represent very large numbers in
// exponential form.
if (s.contains('e')) {
// Ignore values in exponential form.
return s;
}
// Ignore unrecognized values (e.g. NaN, infinity, -infinity).
var i = s.indexOf('.');
if (i == -1) {
return s;
}
return s.substring(0, i fractionDigits 1);
}
}
void main() {
var values = [
1.151,
1.15,
1.1999,
-1.1999,
1.0,
1e21,
1e-20,
double.nan,
double.infinity,
double.negativeInfinity,
];
for (var v in values) {
print(v.toStringAsTruncated(1));
}
}
Another approach one might consider is to multiply by pow(10, fractionalDigits)
, use double.truncateToDouble
, divide by the power-of-10 used earlier, and then use .toStringAsFixed(fractionalDigits)
. That could work for human-scaled values, but it could generate unexpected results for very large values due to precision loss from floating-point arithmetic. (This approach would work if you used package:decimal
instead of double
, though.)
1 Not rounding seems especially bad given that using double
s to represent fractional base-10 numbers is inherently imprecise. For example, since the closest IEEE-754 double-precision floating number to 0.7 is 0.6999999999999999555910790149937383830547332763671875, do you really want 0.7.toStringAsTruncated(1)
to return '0.6'
instead of '0.7'
?