【问题标题】:Determine Number of Decimal Place using BigDecimal使用 BigDecimal 确定小数位数
【发布时间】:2010-02-19 12:08:11
【问题描述】:

我有兴趣拥有以下getNumberOfDecimalPlace 功能:

System.out.println("0 = " + Utils.getNumberOfDecimalPlace(0));          // 0
System.out.println("1.0 = " + Utils.getNumberOfDecimalPlace(1.0));      // 0
System.out.println("1.01 = " + Utils.getNumberOfDecimalPlace(1.01));    // 2
System.out.println("1.012 = " + Utils.getNumberOfDecimalPlace(1.012));  // 3
System.out.println("0.01 = " + Utils.getNumberOfDecimalPlace(0.01));    // 2
System.out.println("0.012 = " + Utils.getNumberOfDecimalPlace(0.012));  // 3

我可以知道如何使用BigDecimal 来实现getNumberOfDecimalPlace 吗?

以下代码无法按预期工作:

public static int getNumberOfDecimalPlace(double value) {
    final BigDecimal bigDecimal = new BigDecimal("" + value);
    final String s = bigDecimal.toPlainString();
    System.out.println(s);
    final int index = s.indexOf('.');
    if (index < 0) {
        return 0;
    }
    return s.length() - 1 - index;
}

打印以下内容:

0.0
0 = 1
1.0
1.0 = 1
1.01
1.01 = 2
1.012
1.012 = 3
0.01
0.01 = 2
0.012
0.012 = 3

但是,对于案例 0、1.0,它并不能很好地工作。我期望结果是“0”。但结果却是“0.0”和“1.0”。这将返回“1”作为结果。

【问题讨论】:

  • 您打算将输入参数设为 BigDecimal 还是仅在内部使用 BigDecimal?因为您的代码示例中的输入参数只是一个双精度参数。
  • 我发现这里的解决方案对实现该功能很有用:stackoverflow.com/questions/6264576/…
  • 这是哪个“Utils”?

标签: java


【解决方案1】:

结合TurismoRobertuser1777653's 的答案,我们得到:

int getNumberOfDecimalPlaces(BigDecimal bigDecimal) {
    return Math.max(0, bigDecimal.stripTrailingZeros().scale());
}
  • stripTrailingZeros() 确保不计算尾随零(例如,1.0 有 0 个小数位)。
  • scale()String.indexOf() 更高效。
  • 负数scale() 表示零小数位。

你有它,两全其美。

【讨论】:

  • 这可能是错误的,因为10stripTrailingZeros() 之后变成了1E1,然后scale()-1,而不是0
  • @EricWang 该代码使用Math.max(0, ...),因此您最终会得到0
  • 好吧,没注意到。
  • 当您使用双精度值构造 BigDecimal 时,这将失败。例如new BigDecimal(123.123).stripTrailingZeros().scale() 将返回 46
  • @NandkumarTekale 你不应该使用double 构造函数。见stackoverflow.com/q/12218515/14731
【解决方案2】:

这段代码:

int getNumberOfDecimalPlaces(BigDecimal bigDecimal) {
    String string = bigDecimal.stripTrailingZeros().toPlainString();
    int index = string.indexOf(".");
    return index < 0 ? 0 : string.length() - index - 1;
}

...通过了这些测试:

assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.001")), equalTo(3));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.01")), equalTo(2));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("0.1")), equalTo(1));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.000")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.00")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1.0")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("1")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10")), equalTo(0));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.1")), equalTo(1));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.01")), equalTo(2));
assertThat(getNumberOfDecimalPlaces(new BigDecimal("10.001")), equalTo(3));

...如果这确实是您想要的。其他回复是正确的,您必须一直使用 BigDecimal 而不是 double/float。

【讨论】:

  • 这是页面上唯一的正确答案。为什么?因为@Robert 使用 stripTrailingZeros() 来区分那些真正重要的小数位。做得好,罗伯特。
  • @Robert Atkins 警告,给我 1 和“0.0”,应该是 0。
  • scale() &lt;= 0signum() == 0可以返回0,也可以避免一些toString()这样的事情。
【解决方案3】:

不用转成String,直接用scale应该会更高效:

  private int getNumberOfDecimalPlaces(BigDecimal bigDecimal)
  {
    int scale =  bigDecimal.stripTrailingZeros().scale();
    return scale>0?scale:0;
  }

【讨论】:

  • 这应该是被接受的答案。 “。”不是世界上唯一的小数分隔符。
【解决方案4】:

如果你真的得到双打,我建议在创建 BigDecimal 之前先将它们格式化为字符串。至少这对我有用:How to check if a double has at most n decimal places?

根据您期望的位数,您可以使用标准格式,如

String.valueOf(doubleValue);

或者您可以使用专门的格式来避免指数格式

DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);

当您有BigDecimal 时,您只需调用scale() 即可获取小数位数。

【讨论】:

  • scale() 的问题是它无法区分小数位和有效小数位。根据 OP 的请求,当它应该返回 0 时,它会为“1.0”返回“1”。
【解决方案5】:

应该这样做

int getNumberOfDecimalPlace(BigDecimal number) {
    int scale = number.stripTrailingZeros().scale();
    return scale > 0 ? scale : 0;
}

【讨论】:

    【解决方案6】:

    错误的不是您的代码,而是您的期望。 double 基于二进制浮点表示,完全不适合准确表示小数。十进制 0.1 例如以二进制表示时具有无限位数,因此它会被截断,当转换回十进制时,您会在最低有效位中出现错误。

    如果您只使用BigDecimal,您的代码将按预期工作。

    【讨论】:

    • "如果您只使用 BigDecimal,您的代码将按预期工作。" --> ?但它不适用于我的情况。
    • @Yan:您没有专门使用 BigDecimal。您不得使用任何 doublefloat包括这些类型的任何文字。
    • @Yan:它不起作用,因为您使用 double 作为输入参数类型。您必须完全避免使用双精度,因为第二次将十进制值放入双精度(即使作为中间结果),您已经损坏了它。
    • 这个答案的问题是,即使将 OP 的代码转换为仅使用 BigDecimals,它仍然无法正常工作。为什么不?因为它不区分小数位和有效小数位。
    【解决方案7】:

    试试这个:

    Math.floor(Math.log(x) / Math.log(10))
    0.001 = -3
    0.01  = -2
    0.1   = -1  
    1     = 0  
    10    = 1  
    100   = 2
    

    【讨论】:

    • 这有助于获取值中第一个有效数字的位置。注意:但是对于像 1.124 这样的值,我想得到小数位数,它不起作用。
    【解决方案8】:

    看看 BigDecimal 的 javadoc 怎么样。我不确定,但我会试试 getScale 和 getPercision。

    【讨论】:

      【解决方案9】:

      获得具有指定小数位数的 BigDecimal 的最佳方法是对其使用 setscale 方法。就我个人而言,我也喜欢使用该方法的舍入版本(请参见下面的链接): http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html#setScale(int,%20int)

      如果您想获取 BigDecimal 当前设置的小数位数,请调用相关的 scale() 方法。

      【讨论】:

      • 我对获得“具有指定小数位数的 BigDecimal”不感兴趣。我很想知道“有多少个小数位数的双精度数”
      • 啊....这是一个更大的问题。问题是双重是不精确的。例如,您可以将 double 的值设置为 1.25,但是当您稍后查看它时,该值返回为 1.24999999。这是因为 Java 存储它的方式。这就是我尽可能避免双打的原因。
      • 描述是正确的,例子不是很好。 1.25 可以精确地表示为double。 1.2 比如不能.
      • @Joachim 是的......对不起。我今天一定是喝咖啡了:-)。
      【解决方案10】:

      目前我发现的最佳选择(不需要 toString + index):

      public static int digitsStripTrailingZero(BigDecimal value)
      {
          return digits(value.stripTrailingZeros());
      }
      
      public static int digits(BigDecimal value)
      {
          return Math.max(0, value.scale());
      }
      

      【讨论】:

        【解决方案11】:

        Michael Borgwardt 的答案是正确的。只要您使用任何 double 或 float,您的值就已经损坏了。

        提供代码示例:

        System.out.println("0 = " + BigDecimalUtil.getNumberOfDecimalPlace("0")); // 0
        System.out.println("1.0 = " + BigDecimalUtil.getNumberOfDecimalPlace("1.0")); // 0
        System.out.println("1.01 = " + BigDecimalUtil.getNumberOfDecimalPlace(new BigDecimal("1.01"))); // 2
        System.out.println("1.012 = " + BigDecimalUtil.getNumberOfDecimalPlace(new BigDecimal("1.012"))); // 3
        System.out.println("0.01 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.01")); // 2
        System.out.println("0.012 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.012")); // 3
        System.out.println("0.00000000000000000012 = " + BigDecimalUtil.getNumberOfDecimalPlace("0.00000000000000000012")); // 20
        

        还有一个 getNumberOfDecimalPlace 的重载版本,因此您可以将它与 BigDecimal 或 String 一起使用:

        public static int getNumberOfDecimalPlace(String value) {
            final int index = value.indexOf('.');
            if (index < 0) {
                return 0;
            }
            return value.length() - 1 - index;
        }
        
        public static int getNumberOfDecimalPlace(BigDecimal value) {
            return getNumberOfDecimalPlace(value.toPlainString());
        }
        

        【讨论】:

        • 你确定 BigDecimalUtil.getNumberOfDecimalPlace("1.0")) 会返回 0?
        • 这个答案的问题是它无法区分小数位和有效小数位。正如@Cheok 正确指出的那样,根据 OP 的请求,当它应该返回 0 时,它会为“1.0”返回“1”。
        【解决方案12】:

        为什么不直接更改您的代码以获得双位小数?

        public static int getNumberOfDecimalPlace(double value) {
            //For whole numbers like 0
            if (Math.round(value) == value) return 0;
            final String s = Double.toString(value);
            System.out.println(s);
            final int index = s.indexOf('.');
            if (index < 0) {
               return 0;
            }
            return s.length() - 1 - index;
        }
        

        【讨论】:

        • @Valentin:因为使用 double 是根本问题。这是朝着完全错误的方向迈出的一步。
        • 它不适用于具有大量小数位的 double,例如 0.0000000000000000000000000000001。 Double.toString(double) 将返回 1.0E-31。你的整个逻辑都失败了
        • 好吧,一开始并没有这样看(我不是回答者,我只是想知道:))
        • @Michael 好的,很好。无需反对,因为这只是一个建议。他似乎将双打传给了它。根据其他 cmets,他似乎只想要 double 值的小数位。
        • 我接受您的回答,因为您是唯一符合我要求的人。我进行了修改,使用 DecimalFormat decimalFormat = new DecimalFormat("0.########"); final String s = decimalFormat.format(value);
        猜你喜欢
        • 2017-07-26
        • 1970-01-01
        • 2011-05-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-19
        • 2019-08-20
        相关资源
        最近更新 更多