【问题标题】:Determine smallest floating point type that can hold a string value确定可以保存字符串值的最小浮点类型
【发布时间】:2014-09-14 09:19:51
【问题描述】:

我正在研究一种将字符串转换为适当的Number 类型的方法,具体取决于数字的格式。如果数字看起来是一个浮点值,那么我需要在不牺牲精度的情况下返回我可以使用的最小类型(FloatDoubleBigDecimal)。

根据How many significant digits have floats and doubles in java?(和其他资源),我了解到Float 的尾数值有 23 位。基于此,我使用以下方法返回给定值的位长:

private static int getBitLengthOfSignificand(String integerPart,
    String fractionalPart) {
  return new BigInteger(integerPart + fractionalPart).bitLength();
}

如果这个测试的结果低于 24,我返回一个Float。如果低于 53,我返回 Double,否则返回 BigDecimal

但是,当我考虑Float.MAX_VALUE(即3.4028235E38)时,我对结果感到困惑。根据我的方法,有效数字的位长为 26(其中 integerPart = 3fractionalPart = 4028235。这会触发我的方法返回 Double,而显然 Float 就足够了。

有人能指出我的想法或实施中的缺陷吗?我的另一个想法是将字符串转换为BigDecimal 并使用floatValue()doubleValue() 缩小,测试溢出(由无限值表示)。但这会失去精度,所以不适合我。

【问题讨论】:

  • @tmyklebu 你能解释一下这是怎么重复的吗?我问了一个关于类型转换的非常具体的问题,你似乎已经链接到一个关于浮点数不准确性的通用问题。两者有什么关系?
  • 如果不“牺牲精度”,您就无法做到这一点。在3.4028235e38 = 0xFFFFFF2A6D7FC1BEF94AD08780000000 和各种十进制情况下,计算精度损失的方法是通过计算二进制展开式,这使得这个问题与另一个问题基本相同。

标签: java floating-point type-conversion


【解决方案1】:

有效数字以二进制形式存储,只有在不让您感到困惑的情况下,您才能将其视为十进制表示的数字。

指数是一个二进制指数,它不代表乘以十的幂,而是乘以二的幂。出于这个原因,您用作示例的数字中的E38 只是为了方便:实际有效数字是二进制的,应该乘以 2 的幂来获得实际数字。 2 的幂和 10 的幂不相同,因此“3.4028235”不是真正的有效数字。 Float.MAX_VALUE 的真正有效位是十六进制的 0x1.fffffe,它的相关指数是 127,这意味着 Float.MAX_VALUE 实际上是 0x1.fffffe * 2127

查看十进制表示以选择二进制浮点类型来输入值,正如您尝试做的那样,是行不通的。一方面,一个人肯定会从float 中恢复的十进制位数不同于人们可能需要写入的十进制位数以区分float 与其邻居(分别为6 和9)。您选择编写“3.4028235E38”,但您可以编写 3.40282E38,对于您的算法,它看起来更容易表示,但实际上并非如此。当人们写“3.4028235E38”是float类型的最大有限值时,他们的意思是如果你把这个十进制数四舍五入到float,你会得到最大的浮点数。如果您将“3.4028235E38”解析为双精度数,它甚至不会等于Float.MAX_VALUE

换种说法:Float.MAX_VALUE 的另一种写法是3.4028234663852885981170418348451692544E38。它仍然可以表示为float(它表示与3.4028235E38 完全相同的值)。看起来它有很多数字,因为这些是 十进制 数字,出现在 十进制指数 中,而实际上该数字在内部用二进制指数表示。

(顺便说一句,您的方法不检查指数是否在范围内以表示所选类型中的数字,这是类型能够表示字符串中的数字的另一个条件。)

【讨论】:

  • 感谢您对潜在问题的详细描述。对于解决我的问题的更好方法,您有什么建议吗?
  • @Duncan 在我看来,您试图将字符串信息作者的意图转换为格式选择。如果字符串的作者写的是“0.1”,那么他可能真的只关心这个数字,0.096 到 0.14 之间的任何值都是他可以接受的。那么float 是一个很好用的类型。但也许作者的意思是“0.100000000000”并且不符合惯例省略了尾随的零。在这种情况下,float 不合适,但 double 仍然有效。
  • @Duncan 如果这是你想要做的,那么就不要在 3.4028235E38 上测试你的函数,这是一个特例:写这个字符串的人正好意味着某个 float (最大的有限float),这就是他们写多达 8 个小数的原因。但是他们故意写出可表示的float 的小数,因为他们要转换为十进制的数字是float。这个数字是正常的,它有小数位数,没有输入其实际值的详细信息,看起来需要一个 double 类型才能准确包含。
  • @Duncan 3.4028235E38 仅碰巧接近一个特定的float,但不会有另一个具有相同小数位数的十进制表示。
  • @Duncan 如果目标是拥有assertEquals(inputString, myMethod(inputString).toString()),最简单的方法是进行测试,首先是float,然后是double,如果两者都没有,则使用BigDecimal二进制浮点类型设法满足该属性。如果您负担得起两次两次转换(现代计算机上的百万分之一秒),这是最简单的。
【解决方案2】:

我会根据实际值和最接近的float 之间的差异来计算。 BigDecimal 可以精确存储任何有限长度的小数并对其进行算术运算:

String 转换为最接近的float x。如果x 是无限的,但该值具有有限的double 表示,则使用它。

String 完全转换为BigDecimal y

如果y为零,则使用float,它可以准确地表示零。

如果不是,请将float x 转换为BigDecimalz

BigDecimal 中计算(y-z)/z 的绝对值到合理的小数位数。这是由于使用float 而导致的相对舍入误差。如果它对于您的目的足够小,小于您选择的某个值,请使用float。如果没有,请使用double

如果你真的不希望牺牲精度,那就简单多了。转换为floatdouble。比较它们是否相等。比较将在double 中完成。如果它们比较相等,请使用float。如果没有,请使用double

【讨论】:

  • 关于您的最后一段 - 我认为某些输入值需要 BigDecimal 才能准确表示。您建议如何区分这三种情况?
  • 再次检查是否相等。如果转换为floatdouble 然后转换为BigDecimal 给出与直接转换为BigDecimal 相同的数值,浮点或双精度是精确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2019-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-12
  • 1970-01-01
相关资源
最近更新 更多