四舍五入到任意有效数字位数


83

如何将任何数字(不仅是整数> 0)舍入为N个有效数字?

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

1,239,451,回报1,240,000

12.1257并​​返回12.1

.0681并返回.0681

5并返回5

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


似乎问题太笼统了。不同的编程语言具有不同的标准功能来执行此操作。确实不适合重新发明轮子。
Johnny Wong

Answers:


106

这是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;
}

2
感谢您接受我的回答。我刚刚意识到我的回答是在提问后一年以上。这是stackoverflow如此酷的原因之一。您可以找到有用的信息!
Pyrolistical,2010年

2
请注意,对于接近舍入限制的值,这可能会稍微失败。例如,将1.255舍入到3个有效数字应返回1.26,但返回1.25。那是因为1.255 * 100.0是125.499999 ...但是这在使用双打时是可以预期的
cquezel 2014年

哇,我知道这很旧,但是我正在尝试使用它。我有一个要显示为3个有效数字的浮点数。如果float值为1.0,则我将调用您的方法,但即使将float转换为double,它也仍将返回1.0。我希望它返回1。有什么想法吗?
史蒂夫W

3
这个Java代码片断在官方Android例如结束android.googlesource.com/platform/development/+/fcf4286/samples/...
好奇山姆

1
不完美。对于num = -7999999.999999992和n = 2返回-7999999.999999999,但应为-8000000。
邓肯·卡尔弗特

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

但是12.1257给出了12.126
Pyrolistical

1
好的答案,阿特斯。如果n==0:)
sscirrus,2011年

有理由Math.log(n) / Math.LN10而不是做Math.log10(n)吗?

1
@Lee developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... “这是一种新技术,在2015年的ECMAScript(ES6)标准的一部分。” 因此,基本上是兼容性问题。
Ates Goral,2015年

我是否缺少某些东西,或者此答案假设是Math.floor(x) == Math.ceil(x) - 1?因为在什么时候不是x整数?我认为该pow函数的第二个参数应该是sig - Math.ceil(Math.log(n) / Math.LN10)(或仅使用Math.log10
Paul,

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

功率是上面计算出的功率。


关于准确性:Pyrolistical的答案确实更接近实际结果。但是请注意,在任何情况下都不能完全代表12.1。如果按以下方式打印答案:

System.out.println(new BigDecimal(n));

答案是:

Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375

因此,请使用Pyro的答案!


1
该算法似乎容易出现浮点错误。当使用JavaScript实现时,我得到:0.06805-> 0.06810000000000001和12.1-> 12.100000000000001
Ates

12.1本身不能使用浮点数精确表示-这不是此算法的结果。
克劳迪(Claudiu)

1
Java中的这段代码产生12.100000000000001,并且它使用的是可以精确表示12.1的64位双精度。
2009年

4
不管是64位还是128位。您无法使用2的幂的有限和来表示分数1/10,这就是表示浮点数的方式
Claudiu

2
对于那些喜欢的人,基本上Pyrolistical的答案比我的答案更精确,因此浮点数打印算法将打印“ 12.1”而不是“ 12.100000000000001”。他的回答更好,即使我在技术上是正确的,即您不能准确代表“ 12.1”。
克劳迪(Claudiu)

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

1
Number(814301).toPrecision(4) == "8.143e+5"。如果要向用户显示,通常不是您想要的。
Zaz

非常正确,Josh是的,我通常只建议.toPrecision()用于十进制数字,并且应根据您的个人要求使用/检查接受的答案(带有编辑)。
贾斯汀·维尼亚

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


6

这个Java解决方案如何:

double roundToSignificantFigure(双精度整数,整数精度){
 返回新的BigDecimal(num)
            .round(新的MathContext(precision,RoundingMode.HALF_EVEN))
            .doubleValue(); 
}

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

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

这将以科学计数法显示“大”数字,例如15k为1.5e04。
马特

2

JavaScript:

Number( my_number.toPrecision(3) );

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


1

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

  1. 将数字转换为字符串
  2. 从字符串的开头开始,计数数字-前导零并不重要,其他所有内容均如此。
  3. 当您到达“第n个”数字时,向前看下一个数字,如果该数字为5或更高,则向上取整。
  4. 将所有尾随数字替换为零。

1

[2009年10月26日更正]

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

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

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

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

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


不。再次阅读问题。使用您的算法的3个无花果的1239451将错误地产生123951
Pyrolistical

是的,我对其进行了更正,以区分四舍五入到小数位数(小数点右边)与整数位数(左数)之间。
David R Tribble

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

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

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


0

我在Go中需要此功能,因为Go标准库的缺少math.Round()(在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
}

这真是个神秘的数字!但是我找不到错误或问题。这里发生了什么?
迈克尔·汉普顿'18

0

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

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

看这里:

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


-1
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

此代码使用内置的格式化功能,该功能已转换为舍入功能

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.