【问题标题】:Rounding to an arbitrary number of significant digits四舍五入到任意数量的有效数字
【发布时间】:2010-09-17 04:02:28
【问题描述】:

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

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

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

12.1257 并返回 12.1

.0681 并返回 .0681

5 并返回 5

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

【问题讨论】:

  • 似乎问题太笼统了。不同的编程语言有不同的标准功能来做到这一点。确实不适合重新发明轮子。

标签: algorithm rounding significant-digits


【解决方案1】:

总结:

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位,然后根据其余的四舍五入。

我们可以使用 log 来做第一个。

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

所以对于 > 0 的数字,取对数的上限。对于

现在我们有了数字d:第一种情况是7,第二种情况是2,第三种情况是-2。

我们必须将(d-N)th 数字四舍五入。比如:

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

所以,使用火焰兵的答案!

【讨论】:

  • 这个算法似乎容易出现浮点错误。使用 JavaScript 实现时,我得到: 0.06805 -> 0.06810000000000001 和 12.1 -> 12.100000000000001
  • 12.1 本身无法使用浮点精确表示 - 这不是该算法的结果。
  • 这段 Java 代码生成 12.100000000000001,它使用 64 位双精度,可以精确呈现 12.1。
  • 不管是 64 位还是 128 位。您不能使用 2 的有限幂和来表示分数 1/10,这就是浮点数的表示方式
  • 对于那些插话的人,基本上 Pyrolistical 的答案比我的更精确,因此浮点数打印算法打印 '12.1' 而不是 '12.100000000000001'。他的回答更好,即使我在技术上是正确的,你不能准确地表示“12.1”。
【解决方案2】:

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

  1. 将数字转换为字符串
  2. 从开头开始 字符串,计数数字 - 前导零不是 意义重大,其他一切都很重要。
  3. 当您到达“第 n 个”数字时, 窥视下一个数字,如果 它是 5 或更高,向上取整。
  4. 用零替换所有尾随数字。

【讨论】:

    【解决方案3】:

    这里有一个简短的 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 :)
    • 是否有理由使用Math.log(n) / Math.LN10 而不是Math.log10(n)
    • @Lee developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… “这是一项新技术,是 ECMAScript 2015 (ES6) 标准的一部分。”所以,基本上,兼容性问题。
    • 我错过了什么,还是这个答案假设Math.floor(x) == Math.ceil(x) - 1?因为当x 是整数时它不会。我认为pow函数的第二个参数应该是sig - Math.ceil(Math.log(n) / Math.LN10)(或者直接使用Math.log10
    【解决方案4】:

    这不是“短而甜”的 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"。如果您将其展示给用户,通常不是您想要的。
    • 非常真实,乔希,是的,我通常建议 .toPrecision() 仅用于十进制数字,并且应根据您的个人要求使用/审查接受的答案(带编辑)。
    【解决方案5】:

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

    【讨论】:

    • 感谢您接受我的回答。我刚刚意识到我的答案是在提出问题一年多之后。这也是 stackoverflow 如此酷的原因之一。您可以找到有用的信息!
    • 请注意,对于接近轮次限制的值,这可能会稍微失败。例如,将 1.255 舍入到 3 位有效数字应返回 1.26 但返回 1.25。那是因为 1.255 * 100.0 是 125.499999...但是在使用双打时这是可以预料的
    • 哇,我知道这是旧的,但我正在尝试使用它。我有一个浮点数,我想显示为 3 个有效数字。如果浮点值为 1.0,我调用你的方法,但它仍然返回 1.0,即使我将浮点数转换为双精度值。我希望它返回为 1。有什么想法吗?
    • 这个 java sn-p 最终出现在官方 android 示例 android.googlesource.com/platform/development/+/fcf4286/samples/…
    • 不完美。对于 num = -7999999.999999992 和 n = 2 返回 -7999999.999999999 但应该是 -8000000。
    【解决方案6】:

    [更正,2009-10-26]

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

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

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

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

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

    【讨论】:

    • 不。再次阅读问题。 1239451 与 3 sig figs 使用您的算法将错误地产生 123951
    • 是的,我更正了它以区分舍入到 小数 位数(小数点右侧)与 整数 数位数(向左)。
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:
      /**
       * 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);
      }
      

      【讨论】:

        【解决方案9】:

        这个java解决方案怎么样:

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

        【讨论】:

          【解决方案10】:

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

          roundToSignificantFigures(1.234E-310, 3);
          

          那么变量power 的值将是3 - (-309) = 312。因此,变量magnitude 将变为Infinity,从那时起它都是垃圾。幸运的是,这不是一个无法克服的问题:只有 因素 magnitude 会溢出。真正重要的是 product 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; }

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              【解决方案12】:
              public static double roundToSignificantDigits(double num, int n) {
                  return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
              }
              

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

              【讨论】:

                【解决方案13】:

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

                【讨论】:

                  【解决方案14】:

                  这晚了 5 年,但我会分享给其他仍然有同样问题的人。我喜欢它,因为它很简单,并且在代码方面没有计算。 请参阅Built in methods for displaying Significant figures 了解更多信息。

                  如果您只想打印出来。

                  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。
                  【解决方案15】:

                  JavaScript:

                  Number( my_number.toPrecision(3) );
                  

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

                  【讨论】:

                    【解决方案16】:

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

                    【讨论】:

                      【解决方案17】:

                      我在 Go 中需要这个,由于 Go 标准库缺少 math.Round()(在 go1.10 之前),这有点复杂。所以我也不得不鞭打它。这是我对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
                      }
                      

                      【讨论】:

                      • 这得到了一个神秘的反对票!但我找不到其中的错误或问题。这是怎么回事?
                      【解决方案18】:

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

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

                      看这里:

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

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2011-05-31
                        • 2015-11-12
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2022-06-15
                        相关资源
                        最近更新 更多