如何在小数点后将 Dart 中的双精度数舍入到给定的精度?
Posted
技术标签:
【中文标题】如何在小数点后将 Dart 中的双精度数舍入到给定的精度?【英文标题】:How do you round a double in Dart to a given degree of precision AFTER the decimal point? 【发布时间】:2015-04-09 18:03:28 【问题描述】:给定一个双精度数,我想将它舍入到小数点后给定的精度点数,类似于 php 的 round() 函数。
我可以在 Dart 文档中找到的最接近的东西是 double.toStringAsPrecision(),但这不是我所需要的,因为它在总精度点中包含小数点 之前 的数字.
例如,使用 toStringAsPrecision(3):
0.123456789 rounds to 0.123
9.123456789 rounds to 9.12
98.123456789 rounds to 98.1
987.123456789 rounds to 987
9876.123456789 rounds to 9.88e+3
随着数字的大小增加,我相应地失去了小数点后的精度。
【问题讨论】:
【参考方案1】:请参阅num.toStringAsFixed() 的文档。
字符串 toStringAsFixed(int fractionDigits)
返回 this 的小数点字符串表示。
在计算字符串表示之前将其转换为双精度。
如果 this 的绝对值大于或等于 10^21,则此方法返回由 this.toStringAsExponential() 计算的指数表示。例子:
1000000000000000000000.toStringAsExponential(3); // 1.000e+21
否则,结果是最接近的字符串表示形式,小数点后正好有 fractionDigits 位。如果 fractionDigits 等于 0,则省略小数点。
参数 fractionDigits 必须是满足:0
例子:
1.toStringAsFixed(3); // 1.000
(4321.12345678).toStringAsFixed(3); // 4321.123
(4321.12345678).toStringAsFixed(5); // 4321.12346
123456789012345678901.toStringAsFixed(3); // 123456789012345683968.000
1000000000000000000000.toStringAsFixed(3); // 1e+21
5.25.toStringAsFixed(0); // 5
【讨论】:
必须是公认的+参考+解释+示例 我认为 toStringAsFixed() 是由于其不一致而使用的舍入方法。例如,尝试5.550.toStringAsFixed(1)
在舍入到 0 位时也要小心,因为在这种情况下,表达式返回 int
而不是 double
,例如num.parse(50.123.toStringAsFixed(0)) is int
- 这可以通过在其末尾添加 .toDouble()
来解决。
@brendan 假设 尝试 存储 5.55 的 double
实际上是 5.5499999999999998... 那么toStringAsFixed(1)
返回正确答案 (5.5)。【参考方案2】:
num.toStringAsFixed()
回合。这会将您的 num (n) 转换为具有您想要的小数位数 (2) 的字符串,然后在一行代码中将其解析回您的 num:
n = num.parse(n.toStringAsFixed(2));
【讨论】:
这是最快的方法。但是,dart 没有直接的方法来做到这一点,这很愚蠢 扩展方法:extension NumberRounding on num num toPrecision(int precision) return num.parse((this).toStringAsFixed(precision));
@ThinkDigital Dart 确实有一个直接的方法:toStringAsFixed
。它确实没有有直接的方法将double
四舍五入到指定的小数位数并将结果保持为double
,因为这是一件愚蠢的事情.您想要的十进制值很可能无法完全表示为 double
。
我认为这个答案应该澄清这段代码的实际作用是将 最近的 double
返回到四舍五入的值。【参考方案3】:
上述解决方案没有适当地对数字进行四舍五入。我用:
double dp(double val, int places)
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
【讨论】:
确认,这轮dp(5.550, 1)
正确到 5.6
- 不像最流行的答案,它给出了一个稍微不正确的答案 5.5
正如@brendan 指出的那样。
不适用于 dp(73.4750, 2) 它返回 73.47 而不是 73.48 calculatorsoup.com/calculators/math/… 查看此链接
@DeepShah 我相信这个答案 -> [***.com/a/66840734/3002719] 解决了您对“number 5 rounding rule”问题的担忧以上。
这本质上是有问题的,因为将 IEEE-754 浮点数(二进制)四舍五入到十进制精度是没有意义的;许多小数十进制数不能用二进制表示。另见Is floating point math broken?。
能否将 4436.605 转换为 4436.61(有 2 个位置)【参考方案4】:
直接方式:
double d = 2.3456789;
String inString = d.toStringAsFixed(2); // '2.35'
double inDouble = double.parse(inString); // 2.35
使用扩展:
extension Ex on double
double toPrecision(int n) => double.parse(toStringAsFixed(n));
用法:
void main()
double d = 2.3456789;
double d1 = d.toPrecision(1); // 2.3
double d2 = d.toPrecision(2); // 2.35
double d3 = d.toPrecision(3); // 2.345
【讨论】:
我不明白这是如何工作的。为什么 double.parse 会返回 double ,因为我们没有传递对它的引用? (我在想this.parse(toStringAsFixed(n));
之类的东西)
@kyu double.parse
是 static
类型的 static
方法。
我认为这个答案应该澄清这段代码的实际作用是将 最近的 double
返回到四舍五入的值。【参考方案5】:
var price = 99.012334554;
price = price.toStringAsFixed(2);
print(price); // 99.01
那是 dart 的 ref。 参考:https://api.dartlang.org/stable/2.3.0/dart-core/num/toStringAsFixed.html
【讨论】:
这个答案不会给Greg Lowe's much earlier answer添加任何东西。【参考方案6】:void main()
int decimals = 2;
int fac = pow(10, decimals);
double d = 1.234567889;
d = (d * fac).round() / fac;
print("d: $d");
打印: 1.23
【讨论】:
这是目前最好的方法。四舍五入到像 0.01 这样本身不是双精度的精度并不是微不足道的。结果甚至可能根本无法表示为双精度数。我强烈建议对小数精度很重要的任何事物(例如金钱)使用整数。 另外值得指出的是,如果编译成 js,那么 Dart 整数也将开始失去精度,你需要使用如下包:pub.dartlang.org/packages/bignum 这种方法感觉是目前最好、最优雅的方法。 double 不能赋值给 int 错误发生在第 3 行【参考方案7】:我使用toStringAsFixed()
方法,将数字四舍五入为小数点后的特定数字
例如:
double num = 22.48132906
当我将它四舍五入为这样的两个数字时:
print(num.toStringAsFixed(2)) ;
它打印了22.48
当我四舍五入到一个数字时,它打印出22.5
【讨论】:
这个答案不会给Greg Lowe's much earlier answer添加任何东西。【参考方案8】:@andyw 使用 Dart 扩展方法的修改答案:
extension Precision on double
double toPrecision(int fractionDigits)
double mod = pow(10, fractionDigits.toDouble());
return ((this * mod).round().toDouble() / mod);
用法:
var latitude = 1.123456;
var latitudeWithFixedPrecision = latitude.toPrecision(3); // Outputs: 1.123
【讨论】:
【参考方案9】:要将 Dart 中的双精度数舍入到小数点后的给定精度,可以使用 dart toStringAsFixed()
方法中的内置解决方案,但必须将其转换回双精度
void main()
double step1 = 1/3;
print(step1); // 0.3333333333333333
String step2 = step1.toStringAsFixed(2);
print(step2); // 0.33
double step3 = double.parse(step2);
print(step3); // 0.33
【讨论】:
此答案不会向Yngvar Natland's much earlier answer 添加任何内容。另外,我认为这个答案应该澄清这段代码实际上所做的是将 nearestdouble
返回到舍入值。【参考方案10】:
您可以简单地将值乘以 100,然后将其四舍五入,然后再除以 100。
(number * 100).round() / 100.0;
【讨论】:
【参考方案11】:double value = 2.8032739273;
String formattedValue = value.toStringAsFixed(3);
【讨论】:
请为您的答案添加更多解释,以造福其他用户 这在语法上不正确,toStringAsFixed
带了一个参数【参考方案12】:
您可以使用toStringAsFixed
来显示小数点后的有限位数。 toStringAsFixed
返回小数点字符串表示。 toStringAsFixed
接受一个名为 fraction Digits
的参数,这是我们想要显示的小数点后的位数。这是它的使用方法。
double pi = 3.1415926;
const val = pi.toStringAsFixed(2); // 3.14
【讨论】:
【参考方案13】:上述解决方案并非适用于所有情况。对我的问题有用的是这个解决方案将四舍五入您的数字(0.5 到 1 或 0.49 到 0)并保留它没有任何小数:
输入: 12.67
double myDouble = 12.67;
var myRoundedNumber; // Note the 'var' datatype
// Here I used 1 decimal. You can use another value in toStringAsFixed(x)
myRoundedNumber = double.parse((myDouble).toStringAsFixed(1));
myRoundedNumber = myRoundedNumber.round();
print(myRoundedNumber);
输出: 13
This link has other solutions too
【讨论】:
【参考方案14】:您可以创建一个可重复使用的函数,该函数接受要格式化的 numberOfDecimal,并利用 toStringAsFixed() 方法格式化数字并将其转换回双精度数。
仅供参考,toStringAsFixed 方法不会对以 5 结尾的数字进行四舍五入(例如:toStringAsFixed 将 2.275 舍入为 2.27 而不是 2.28)。 This is the default behaviour of dart toStringAsFixed method (similar to Javascript toFixed)
作为一种解决方法,我们可以在最后一个十进制数后面加 1(例如:将 0.0001 加到 2.275 变成 2.2751 和 2.2751 将正确四舍五入为 2.28)
double roundOffToXDecimal(double number, int numberOfDecimal = 2)
// To prevent number that ends with 5 not round up correctly in Dart (eg: 2.275 round off to 2.27 instead of 2.28)
String numbersAfterDecimal = number.toString().split('.')[1];
if (numbersAfterDecimal != '0')
int existingNumberOfDecimal = numbersAfterDecimal.length;
number += 1 / (10 * pow(10, existingNumberOfDecimal));
return double.parse(number.toStringAsFixed(numberOfDecimal));
// Example of usage:
var price = roundOffToXDecimal(2.275, numberOfDecimal: 2)
print(price); // 2.28
【讨论】:
【参考方案15】:只需在 double 上写这个扩展
extension Round on double
double roundToPrecision(int n)
int fac = pow(10, n);
return (this * fac).round() / fac;
【讨论】:
但是您显然需要对int fac
做点什么。 pow()
返回 num
,而不是 int
。所以,toInt()
.【参考方案16】:
我认为接受的答案不是完美的解决方案,因为它转换为string
。
如果您不想转换为 string
并转换回 double
,请使用
double.toPrecision(decimalNumber)
来自 GetX 包。
如果你不想仅仅为此使用 GetX(我强烈推荐 GetX,它会改变你的生活)你可以复制并粘贴它。
当您想使用扩展时,请记住导入文件。
import 'dart:math';
extension Precision on double
double toPrecision(int fractionDigits)
var mod = pow(10, fractionDigits.toDouble()).toDouble();
return ((this * mod).round().toDouble() / mod);
【讨论】:
【参考方案17】:如果你想对文本内的双精度值进行四舍五入。
文本('$carpetprice.toStringAsFixed(3)',),
【讨论】:
【参考方案18】:如果您在结果小数全为零时不想要任何小数,则可以使用以下方法:
String fixedDecimals(double d, int decimals, bool removeZeroDecimals = true)
double mod = pow(10.0, decimals);
double result = ((d * mod).round().toDouble() / mod);
if( removeZeroDecimals && result - (result.truncate()) == 0.0 ) decimals = 0;
return result.toStringAsFixed(decimals);
如果输入是 9.004
并且您需要 2 个小数,这将简单地输出 9
而不是 9.00
。
【讨论】:
【参考方案19】:如果使用动态类型的数据。你可以使用它。
typeDecimal(data) => num.parse(data.toString()).toStringAsFixed(2);
【讨论】:
【参考方案20】:我在 double 上做了这个扩展
import 'dart:math';
extension DoubleExtension on double
/// rounds the double to a specific decimal place
double roundedPrecision(int places)
double mod = pow(10.0, places) as double;
return ((this * mod).round().toDouble() / mod);
/// good for string output because it removes trailing zeros
/// and sometimes periods
/// example 5.0 -function-> "5"
String roundedPrecisionToString(int places)
double mod = pow(10.0, places) as double;
String doubleToString = ((this * mod).round().toDouble() / mod).toString();
RegExp trailingZeros = RegExp(r'^[0-9]+.0+$');
if (trailingZeros.hasMatch(doubleToString))
doubleToString = doubleToString.split(".")[0];
return doubleToString;
这是通过的测试。
import 'package:flutter_test/flutter_test.dart';
import 'package:player_agent/utils/double_extension.dart';
void main()
group("rounded precision", ()
test("rounding to 0 place results in an int", ()
double num = 5.1234;
double num2 = 5.8234;
expect(num.roundedPrecision(0), 5);
expect(num2.roundedPrecision(0), 6);
);
test("rounding to 1 place rounds correctly to 1 place", ()
double num = 5.12;
double num2 = 5.15;
expect(num.roundedPrecision(1), 5.1);
expect(num2.roundedPrecision(1), 5.2);
);
test(
"rounding a number to a precision that is more accurate than the origional",
()
double num = 5;
expect(num.roundedPrecision(5), 5);
);
);
group("rounded precision returns the correct string", ()
test("rounding to 0 place results in an int", ()
double num = 5.1234;
double num2 = 5.8234;
expect(num.roundedPrecisionToString(0), "5");
expect(num2.roundedPrecisionToString(0), "6");
);
test("rounding to 1 place rounds correct", ()
double num = 5.12;
double num2 = 5.15;
expect(num.roundedPrecisionToString(1), "5.1");
expect(num2.roundedPrecisionToString(1), "5.2");
);
test("rounding to 2 places rounds correct", ()
double num = 5.123;
double num2 = 5.156;
expect(num.roundedPrecisionToString(2), "5.12");
expect(num2.roundedPrecisionToString(2), "5.16");
);
test("cut off all trailing zeros (and periods)", ()
double num = 5;
double num2 = 5.03000;
expect(num.roundedPrecisionToString(5), "5");
expect(num2.roundedPrecisionToString(5), "5.03");
);
);
【讨论】:
【参考方案21】:将double
(一个 IEEE-754 二进制 浮点数)四舍五入到特定数量的十进制 位数本身就是有问题的。
就像 1/3 这样的分数不能用有限个十进制数字精确表示一样,很多(实际上是无限多个)十进制数也不能用有限个二进制数字。例如,十进制数 0.1 不能用二进制精确表示。虽然您可以尝试将 0.09999 舍入为 0.1,但实际上是 0.1000000000000000055511151231257827021181583404541015625。大多数声称以十进制精度舍入 double
s 的其他答案实际上返回 最近 可表示的 double
。
你可以做的是对字符串表示中的值进行四舍五入,这就是double.toStringAsFixed()
所做的。这也是为什么当您打印 0.1 时,如果实现尝试打印用户友好的值,您可能会看到 0.1
。但是,请不要上当:double
的值实际上永远不会是 0.1,如果你用这样不精确的值重复算术,你会累积错误。
请注意,以上所有内容都是浮点数工作方式所固有的,并不是 Dart 特有的。另见:
Is floating point math broken? 如果不理解以上解释,Tom Scott 也发a great video explaining how floating-point works。底线:如果您关心 十进制 精度,请不要使用 二进制 浮点类型。如果您在处理金钱问题,这一点尤其重要。
你应该改用:
整数。例如,如果您正在处理货币,请使用int cents = 123;
,而不是使用double dollars = 1.23;
。然后,您的计算将始终是准确的,并且您只能在向用户显示它们时转换为所需的单位(同样在读取用户输入时以相反的方向转换)。
一种用于表示具有任意精度的十进制数的类型。例如,package:decimal
提供了一个 Decimal
类型。对于这种类型,其他一些答案(例如乘以 100、四舍五入,然后除以 100)将是合适的。 (但实际上你应该直接使用Decimal.round
。)
【讨论】:
使用此包精确舍入:Decimal.parse(this.toString()).round(scale: places).toDouble()
@Paul 你应该去掉toDouble()
。这违背了使用package:decimal
的意义。
可能会有双重回报被质疑的情况,所以我想我应该添加它。另外:这对双精度进行了正确的舍入(可能只有一次)。但是,是的,如果您需要使用双精度/小数进行计算,您显然不应该这样做。
@Paul “正确舍入一个双精度数”是一个无意义的陈述,因为您不能以基数 10 位数字精确舍入 double
。从根本上说,有一些以 10 为底的数字不能精确地表示为 double
s。没有“四舍五入”可以避免这种情况。【参考方案22】:
这个 DART 舍入问题由来已久 (@LucasMeadows),因为很明显它直到现在还没有得到充分解决 (as indicated by @DeepShah's observation)。
众所周知的舍入规则(未解决的问题):
" 以数字 5 结尾的四舍五入:如果结果是偶数,则四舍五入;如果结果是奇数,则四舍五入。 "
所以这里是 DART 代码解决方案:
double roundAccurately(double numToRound, int decimals)
// Step 1 - Prime IMPORTANT Function Parameters ...
int iCutIndex = 0;
String sDeciClipdNTR = "";
num nMod = pow(10.0, decimals);
String sNTR = numToRound.toString();
int iLastDigitNTR = 0, i2ndLastDigitNTR = 0;
print("Round => $numToRound to $decimals Decimal $(decimals == 1) ? "Place" : "Places" !!"); // Deactivate this 'print()' line in production code !!
// Step 2 - Calculate Decimal Cut Index (i.e. string cut length) ...
int iDeciPlaces = (decimals + 2);
if (sNTR.contains('.'))
iCutIndex = sNTR.indexOf('.') + iDeciPlaces;
else
sNTR = sNTR + '.';
iCutIndex = sNTR.indexOf('.') + iDeciPlaces;
// Step 3 - Cut input double to length of requested Decimal Places ...
if (iCutIndex > sNTR.length) // Check that decimal cutting is possible ...
sNTR = sNTR + ("0" * iDeciPlaces); // ... and fix (lengthen) the input double if it is too short.
sDeciClipdNTR = sNTR.substring(0, iCutIndex); // ... then cut string at indicated 'iCutIndex' !!
else
sDeciClipdNTR = sNTR.substring(0, iCutIndex); // Cut string at indicated 'iCutIndex' !!
// Step 4 - Extract the Last and 2nd Last digits of the cut input double.
int iLenSDCNTR = sDeciClipdNTR.length;
iLastDigitNTR = int.parse(sDeciClipdNTR.substring(iLenSDCNTR - 1)); // Extract the last digit !!
(decimals == 0)
? i2ndLastDigitNTR = int.parse(sDeciClipdNTR.substring(iLenSDCNTR - 3, iLenSDCNTR - 2))
: i2ndLastDigitNTR = int.parse(sDeciClipdNTR.substring(iLenSDCNTR - 2, iLenSDCNTR - 1));
// Step 5 - Execute the FINAL (Accurate) Rounding Process on the cut input double.
double dAccuRound = 0;
if (iLastDigitNTR == 5 && ((i2ndLastDigitNTR + 1) % 2 != 0))
dAccuRound = double.parse(sDeciClipdNTR.substring(0, iLenSDCNTR - 1));
else
if (iLastDigitNTR < 5)
dAccuRound = double.parse(sDeciClipdNTR.substring(0, iLenSDCNTR - 1));
else
if (decimals == 0)
sDeciClipdNTR = sNTR.substring(0, iCutIndex - 2);
dAccuRound = double.parse(sDeciClipdNTR) + 1; // Finally - Round UP !!
else
double dModUnit = 1 / nMod;
sDeciClipdNTR = sNTR.substring(0, iCutIndex - 1);
dAccuRound = double.parse(sDeciClipdNTR) + dModUnit; // Finally - Round UP !!
// Step 6 - Run final QUALITY CHECK !!
double dResFin = double.parse(dAccuRound.toStringAsFixed(decimals));
// Step 7 - Return result to function call ...
print("Result (AccuRound) => $dResFin !!"); // Deactivate this 'print()' line in production code !!
return dResFin;
这是一种完全手动的方法(可能有点矫枉过正),但它确实有效。请测试它(用尽),如果我错过了标记,请告诉我。
【讨论】:
更新函数(上图):roundAccurately(73.4750, 2)
返回73.48
!! @deep-shah
这非常复杂。看看这个答案,只使用:Decimal.parse(this.toString()).round(scale: places).toDouble()
【参考方案23】:
我喜欢这样转换我的 => `
num.tryParse("23.123456789")!.toDouble().roundToDouble()
`
【讨论】:
【参考方案24】:你可以调用这个函数来获得黑暗(颤动)的精确度。 double eval -> 想要转换的 double int i -> 要返回的小数点数。
double doubleToPrecision(double eval, int i)
double step1 = eval;//1/3
print(step1); // 0.3333333333333333
String step2 = step1.toStringAsFixed(2);
print(step2); // 0.33
double step3 = double.parse(step2);
print(step3); // 0.33
eval = step3;
return eval;
【讨论】:
【参考方案25】:这很好用
var price=99.012334554
price = price.roundTodouble();
print(price); // 99.01
【讨论】:
以上是关于如何在小数点后将 Dart 中的双精度数舍入到给定的精度?的主要内容,如果未能解决你的问题,请参考以下文章
如何优化 spark 函数以将双精度值舍入到小数点后 2 位?