如何将任何数字(不仅限于大于0的整数)四舍五入到N个有效数字?
例如,如果我想要将数字四舍五入到三位有效数字,我需要一个公式可以处理以下情况:
1,239,451并返回1,240,000
12.1257并返回12.1
.0681并返回.0681
5并返回5
当然,该算法不应被硬编码为仅处理N等于3的情况,虽然这是一个好的开始。
如何将任何数字(不仅限于大于0的整数)四舍五入到N个有效数字?
例如,如果我想要将数字四舍五入到三位有效数字,我需要一个公式可以处理以下情况:
1,239,451并返回1,240,000
12.1257并返回12.1
.0681并返回.0681
5并返回5
当然,该算法不应被硬编码为仅处理N等于3的情况,虽然这是一个好的开始。
以下是在避免12.100000000000001错误的情况下使用Java编写的相同代码。
我还删除了重复的代码,将power
更改为整数类型以避免进行n - d
时出现浮点问题,并使长中间变量更加清晰。
该错误是由于将一个大数乘以一个小数而引起的。 相反,我将两个大小相似的数字相除。
编辑
修复了更多错误。 添加了对0的检查,因为它会导致NaN。 使函数实际上可以处理负数(原始代码无法处理负数,因为负数的对数是一个复数)。
public static double roundToSignificantFigures(double num, int n) {
if(num == 0) {
return 0;
}
final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
final int power = n - (int) d;
final double magnitude = Math.pow(10, power);
final long shifted = Math.round(num*magnitude);
return shifted/magnitude;
}
这是一个简洁明了的JavaScript实现:
function sigFigs(n, sig) {
var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
return Math.round(n * mult) / mult;
}
alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5
n==0
时返回 0 :) - sscirrusMath.log(n) / Math.LN10
而不是 Math.log10(n)
吗? - LeeMath.floor(x) == Math.ceil(x) - 1
?因为当 x
是整数时,它并不是。我认为 pow
函数的第二个参数应该是 sig - Math.ceil(Math.log(n) / Math.LN10)
(或者直接使用 Math.log10
)。 - Paul摘要:
double roundit(double num, double N)
{
double d = log10(num);
double power;
if (num > 0)
{
d = ceil(d);
power = -(d-N);
}
else
{
d = floor(d);
power = -(d-N);
}
return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}
所以你需要找到第一个非零数字的小数位,然后保存接下来的 N-1 个数字,最后基于剩余的数字对第 N 个数字进行四舍五入。
我们可以使用 log 来实现第一步。
log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681 = -1.16
对于大于0的数字,取对数的上限。对于小于0的数字,取对数的下限。
现在我们有数字d
:第一种情况下为7,第二种情况下为2,第三种情况下为-2。
我们必须四舍五入第(d-N)
位数字。类似这样:
double roundedrest = num * pow(10, -(d-N));
pow(1239451, -4) = 123.9451
pow(12.1257, 1) = 121.257
pow(0.0681, 4) = 681
然后进行标准四舍五入:
roundedrest = (int)(roundedrest + 0.5);
撤销 pow 函数。
roundednum = pow(roundedrest, -(power))
这里的 power 是上面计算出来的功率值。
关于精度:Pyrolistical 的答案确实更接近真实结果。但请注意,在任何情况下都无法准确地表示 12.1。如果您将答案打印如下:
System.out.println(new BigDecimal(n));
Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375
所以,使用Pyro的答案!
这个"short and sweet"的JavaScript实现难道不是
Number(n).toPrecision(sig)
例如。alert(Number(12345).toPrecision(3)
抱歉,我这里并不是在挖苦什么,只是使用 Claudiu 的 "roundit" 函数和 JavaScript 中的 .toPrecision 得到了不同的结果,但只在最后一位数字的四舍五入方面有所差异。
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.NET
roundit(8.14301,4) == 8.144
Number(814301).toPrecision(4) == "8.143e+5"
。如果您要向用户展示此内容,通常不是您想要的。 - ZazPyrolistical提供的(非常好的!)解决方案仍然存在问题。在Java中,最大的双精度值约为10^308,而最小值约为10^-324。因此,当您将roundToSignificantFigures
函数应用于接近Double.MIN_VALUE
几个数量级的内容时,可能会遇到问题。例如,当您调用:
roundToSignificantFigures(1.234E-310, 3);
power
的值为3 - (-309) = 312,那么变量magnitude
将变为Infinity
,之后的所有内容都是垃圾。幸运的是,这不是一个难以克服的问题:只有因子magnitude
会溢出,真正重要的是乘积num * magnitude
,它不会溢出。解决这个问题的一种方法是通过将magintude
因子的乘法分成两个步骤来实现:
public static double roundToNumberOfSignificantDigits(double num, int n) {
final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));
if(num == 0) {
return 0;
}
final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
final int power = n - (int) d;
double firstMagnitudeFactor = 1.0;
double secondMagnitudeFactor = 1.0;
if (power > maxPowerOfTen) {
firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
} else {
firstMagnitudeFactor = Math.pow(10.0, (double) power);
}
double toBeRounded = num * firstMagnitudeFactor;
toBeRounded *= secondMagnitudeFactor;
final long shifted = Math.round(toBeRounded);
double rounded = ((double) shifted) / firstMagnitudeFactor;
rounded /= secondMagnitudeFactor;
return rounded;
}
这个Java解决方案怎么样:
double roundToSignificantFigure(double num, int precision){ return new BigDecimal(num) .round(new MathContext(precision, RoundingMode.HALF_EVEN)) .doubleValue(); }该方法将数字四舍五入到指定的有效数字位数。它使用BigDecimal类来执行精确计算,并采用舍入模式HALF_EVEN。
Number( my_number.toPrecision(3) );
< p > Number
函数将会把形如"8.143e+5"
的输出转换为"814300"
。
这是Ates的JavaScript修改版本,可以处理负数。
function sigFigs(n, sig) {
if ( n === 0 )
return 0
var mult = Math.pow(10,
sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
return Math.round(n * mult) / mult;
}
public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
return String.format("%."+significantFigures+"G", bd);
}
如果您想进行转换:
public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
String s = String.format("%."+significantFigures+"G", bd);
BigDecimal result = new BigDecimal(s);
return result;
}
这里是它的实际运用:
BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
以下是Pyrolistical(目前的最佳答案)在Visual Basic.NET中的代码,如果有需要的话:
Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
If (num = 0) Then
Return 0
End If
Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
Dim power As Integer = n - CInt(d)
Dim magnitude As Double = Math.Pow(10, power)
Dim shifted As Double = Math.Round(num * magnitude)
Return shifted / magnitude
End Function