84

如何将任何数字(不仅仅是大于 0 的整数)四舍五入为 N 个有效数字?

例如,如果我想四舍五入到三位有效数字,我正在寻找一个可以采用的公式:

1,239,451 并返回 1,240,000

12.1257 并返回 12.1

.0681 并返回 .0681

5 并返回 5

自然,该算法不应该被硬编码为仅处理 3 的 N,尽管这将是一个开始。

4

18 回答 18

107

这是Java中的相同代码,没有其他答案的12.100000000000001错误

我还删除了重复的代码,更改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;
}
于 2009-10-17T00:20:15.390 回答
16

这是一个简短的 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
于 2008-10-14T19:20:32.587 回答
15

概括:

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);

并撤消战俘。

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 的答案!

于 2008-10-14T18:47:01.700 回答
10

不是“短而甜”的 JavaScript 实现吗

Number(n).toPrecision(sig)

例如

alert(Number(12345).toPrecision(3)

?

抱歉,我在这里不是在开玩笑,只是使用 Claudiu 的“roundit”函数和 JavaScript 中的 .toPrecision 给了我不同的结果,但仅限于最后一位数字的舍入。

JavaScript:

Number(8.14301).toPrecision(4) == 8.143

。网

roundit(8.14301,4) == 8.144
于 2009-04-08T15:26:57.513 回答
8

Pyrolistical(非常好!)的解决方案仍然存在问题。Java 中的最大 double 值约为 10^308,而最小值约为 10^-324。roundToSignificantFigures因此,在将函数应用于 . 的 10 次方以内的东西时,您可能会遇到麻烦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;
}

于 2010-11-19T01:17:06.600 回答
6

这个java解决方案怎么样:

double roundToSignificantFigure(double num, int 精度){
 返回新的 BigDecimal(num)
            .round(new MathContext(precision, RoundingMode.HALF_EVEN))
            .doubleValue();
}
于 2010-08-10T08:00:42.033 回答
3

这是处理负数的 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;
 }
于 2010-06-04T16:18:40.653 回答
3

JavaScript:

Number( my_number.toPrecision(3) );

Number函数会将表单的输出更改"8.143e+5""814300".

于 2015-05-04T22:56:25.090 回答
2

这晚了 5 年,但我会分享给其他人,但仍然有同样的问题。我喜欢它,因为它很简单,并且在代码方面没有计算。有关更多信息,请参阅用于显示重要数字的内置方法

这是如果您只想打印出来。

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);
于 2013-10-22T00:07:22.410 回答
1

您是否尝试过以手动方式对其进行编码?

  1. 将数字转换为字符串
  2. 从字符串的开头开始,计数数字 - 前导零不重要,其他一切都是。
  3. 当您到达“第 n 个”数字时,向前看下一个数字,如果它是 5 或更高,则向上取整。
  4. 用零替换所有尾随数字。
于 2008-10-14T18:52:37.913 回答
1

[更正,2009-10-26]

本质上,对于 N 个有效小数位:

• 将数字乘以 10 N
• 加 0.5
• 截断小数位(即,将结果截断为整数)
• 除以 10 N

对于 N 个有效整数(非小数)数字:

• 将数字除以 10 N
• 加 0.5
• 截断小数位(即,将结果截断为整数)
• 乘以 10 N

您可以在任何具有“INT”(整数截断)运算符的计算器上执行此操作。

于 2009-10-17T00:41:19.240 回答
1
/**
 * Set Significant Digits.
 * @param value value
 * @param digits digits
 * @return
 */
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
    //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
    //# Keep n digits. Replace the rest with zeros.
    //# Round up by one if appropriate.
    int p = value.precision();
    int s = value.scale();
    if (p < digits) {
        value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
    }
    value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
        .movePointRight(p - digits).movePointLeft(s);
    s = (s > (p - digits)) ? (s - (p - digits)) : 0;
    return value.setScale(s);
}
于 2010-06-22T00:25:30.727 回答
1

这是 Visual Basic.NET 中 Pyrolistical 的(当前最佳答案)代码,如果有人需要的话:

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
于 2011-06-21T16:10:45.743 回答
0

这是我在 VB 中提出的:

Function SF(n As Double, SigFigs As Integer)
    Dim l As Integer = n.ToString.Length
    n = n / 10 ^ (l - SigFigs)
    n = Math.Round(n)
    n = n * 10 ^ (l - SigFigs)
    Return n
End Function
于 2013-06-01T16:05:51.537 回答
0

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();

于 2017-03-27T16:22:43.120 回答
0

math.Round()我在 Go 中需要这个,由于 Go 标准库的缺乏(在 go1.10 之前),这有点复杂。所以我也不得不鞭打它。这是我对Pyrolistical 出色答案的翻译:

// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
    return float64(int64(x + 0.5))
}

// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackoverflow.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
    if x == 0 {
        return 0
    }

    power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
    magnitude := math.Pow(10, float64(power))
    shifted := round(x * magnitude)
    return shifted / magnitude
}
于 2018-01-28T02:42:21.370 回答
0

只需使用 FloatToStrF,您就可以避免使用 10 次幂等进行所有这些计算。

FloatToStrF 允许您(除其他外)选择输出值(将是一个字符串)中的精度(有效数字的数量)。当然,您可以然后将 StrToFloat 应用于此以将您的舍入值作为浮点数。

看这里:

http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_FloatToStrF@Extended@TFloatFormat@Integer@Integer.html

于 2020-07-12T13:28:07.470 回答
-1
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

此代码使用内置格式化函数,该函数转换为舍入函数

于 2013-05-19T05:51:47.980 回答