四舍五入到任意数量的有效数字

发布于 2024-07-07 10:25:36 字数 243 浏览 10 评论 0原文

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

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

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

12.1257 并返回 12.1

.0681 并返回 .0681

5 并返回 5

自然,该算法不应该很难 -编码为仅处理 3 中的 N,尽管这只是一个开始。

How can you round any number (not just integers > 0) to N significant digits?

For example, if I want to round to three significant digits, I'm looking for a formula that could take:

1,239,451 and return 1,240,000

12.1257 and return 12.1

.0681 and return .0681

5 and return 5

Naturally the algorithm should not be hard-coded to only handle N of 3, although that would be a start.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

昔梦 2024-07-14 10:25:36

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

Here's the same code in Java without the 12.100000000000001 bug other answers have

I also removed repeated code, changed power to a type integer to prevent floating issues when n - d is done, and made the long intermediate more clear

The bug was caused by multiplying a large number with a small number. Instead I divide two numbers of similar size.

EDIT
Fixed more bugs. Added check for 0 as it would result in NaN. Made the function actually work with negative numbers (The original code doesn't handle negative numbers because a log of a negative number is a complex number)

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;
}
つ可否回来 2024-07-14 10:25:36

这是一个简短而有趣的 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

Here's a short and sweet JavaScript implementation:

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
北陌 2024-07-14 10:25:36

小结:

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。

我们必须对第 (dN) 位数字进行四舍五入。 类似于:

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

SUMMARY:

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

So you need to find the decimal place of the first non-zero digit, then save the next N-1 digits, then round the Nth digit based on the rest.

We can use log to do the first.

log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681  = -1.16

So for numbers > 0, take the ceil of the log. For numbers < 0, take the floor of the log.

Now we have the digit d: 7 in the first case, 2 in the 2nd, -2 in the 3rd.

We have to round the (d-N)th digit. Something like:

double roundedrest = num * pow(10, -(d-N));

pow(1239451, -4) = 123.9451
pow(12.1257, 1)  = 121.257
pow(0.0681, 4)   = 681

Then do the standard rounding thing:

roundedrest = (int)(roundedrest + 0.5);

And undo the pow.

roundednum = pow(roundedrest, -(power))

Where power is the power calculated above.


About accuracy: Pyrolistical's answer is indeed closer to the real result. But note that you can't represent 12.1 exactly in any case. If you print the answers as follows:

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

The answers are:

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

So, use Pyro's answer!

烦人精 2024-07-14 10:25:36

难道不是“简短而甜蜜”的 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

Isn't the "short and sweet" JavaScript implementation

Number(n).toPrecision(sig)

e.g.

alert(Number(12345).toPrecision(3)

?

Sorry, I'm not being facetious here, it's just that using the "roundit" function from Claudiu and the .toPrecision in JavaScript gives me different results but only in the rounding of the last digit.

JavaScript:

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

.NET

roundit(8.14301,4) == 8.144
半岛未凉 2024-07-14 10:25:36

Pyrolistical 的(非常好!)解决方案仍然存在问题。 Java 中的最大 double 值约为 10^308,而最小值约为 10^-324。 因此,当将函数 roundToSignificantFigures 应用于 Double.MIN_VALUE 的 10 次幂范围内时,您可能会遇到麻烦。 例如,当您调用

roundToSignificantFigures(1.234E-310, 3);

时,变量 power 的值为 3 - (-309) = 312。因此,变量 magnitude 将变为 Infinity,从那时起一切都是垃圾。 幸运的是,这并不是一个无法克服的问题:只是因素数量级溢出了。 真正重要的是乘积 num * 大小,并且不会溢出。 解决此问题的一种方法是将乘以因子 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;
}

Pyrolistical's (very nice!) solution still has an issue. The maximum double value in Java is on the order of 10^308, while the minimum value is on the order of 10^-324. Therefore, you can run into trouble when applying the function roundToSignificantFigures to something that's within a few powers of ten of Double.MIN_VALUE. For example, when you call

roundToSignificantFigures(1.234E-310, 3);

then the variable power will have the value 3 - (-309) = 312. Consequently, the variable magnitude will become Infinity, and it's all garbage from then on out. Fortunately, this is not an insurmountable problem: it is only the factor magnitude that's overflowing. What really matters is the product num * magnitude, and that does not overflow. One way of resolving this is by breaking up the multiplication by the factor magintude into two steps:


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

烟火散人牵绊 2024-07-14 10:25:36

这个java解决方案怎么样:

double roundToSignificantFigure(double num, int precision){
 return new BigDecimal(num)
            .round(new MathContext(precision, RoundingMode.HALF_EVEN))
            .doubleValue(); 
}

How about this java solution :

double roundToSignificantFigure(double num, int precision){
 return new BigDecimal(num)
            .round(new MathContext(precision, RoundingMode.HALF_EVEN))
            .doubleValue(); 
}
°如果伤别离去 2024-07-14 10:25:36

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

Here is a modified version of Ates' JavaScript that handles negative numbers.

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;
 }
余生再见 2024-07-14 10:25:36

JavaScript:

Number( my_number.toPrecision(3) );

Number 函数会将 "8.143e+5" 形式的输出更改为 "814300"

JavaScript:

Number( my_number.toPrecision(3) );

The Number function will change output of the form "8.143e+5" to "814300".

雨巷深深 2024-07-14 10:25:36

这件事迟到了 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);

This came 5 years late, but though I'll share for others still having the same issue. I like it because it's simple and no calculations on the code side.
See Built in methods for displaying Significant figures for more info.

This is if you just want to print it out.

public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
    return String.format("%."+significantFigures+"G", bd);
}

This is if you want to convert it:

public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
    String s = String.format("%."+significantFigures+"G", bd);
    BigDecimal result = new BigDecimal(s);
    return result;
}

Here's an example of it in action:

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
可是我不能没有你 2024-07-14 10:25:36

您是否尝试过像手工编写代码一样进行编码?

  1. 将数字转换为字符串
  2. 从开头开始
    字符串,计数数字 - 前导零不是
    重要的是,其他一切都很重要。
  3. 当你到达“第n”位时,
    向前看下一个数字,如果
    为 5 或更高,向上舍入。
  4. 将所有尾随数字替换为零。

Have you tried just coding it up the way you'd do it by hand?

  1. Convert the number to a string
  2. Starting at the beginning of the
    string, count digits - leading zeroes aren't
    significant, everything else is.
  3. When you get to the "nth" digit,
    peek ahead at the next digit and if
    it's 5 or higher, round up.
  4. Replace all of the trailing digits with zeroes.
终难遇 2024-07-14 10:25:36

[更正,2009-10-26]

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

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

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

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

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

[Corrected, 2009-10-26]

Essentially, for N significant fractional digits:

• Multiply the number by 10N
• Add 0.5
• Truncate the fraction digits (i.e., truncate the result into an integer)
• Divide by 10N

For N significant integral (non-fractional) digits:

• Divide the number by 10N
• Add 0.5
• Truncate the fraction digits (i.e., truncate the result into an integer)
• Multiply by 10N

You can do this on any calculator, for example, that has an "INT" (integer truncation) operator.

执笏见 2024-07-14 10:25:36
/**
 * 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);
}
/**
 * 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);
}
你穿错了嫁妆 2024-07-14 10:25:36

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

Here is Pyrolistical's (currently top answer) code in Visual Basic.NET, should anyone need it:

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
如歌彻婉言 2024-07-14 10:25:36

这是我在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

This is one that I came up with in 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
稀香 2024-07-14 10:25:36

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

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

失眠症患者 2024-07-14 10:25:36

我在 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
}

I needed this in Go, which was a bit complicated by the Go standard library's lack of math.Round() (before go1.10). So I had to whip that up too. Here is my translation of Pyrolistical's excellent answer:

// 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
}
夜唯美灬不弃 2024-07-14 10:25:36
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

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

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

This code uses the inbuilt formatting function which is turned to a rounding function

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文