我需要在变量中存储一个带有n位小数的浮点数,但这似乎太复杂了。
如果我运行这段代码:
float testFloat = -18.479999542236328f;
System.out.println(getFloatAsStringIWithNDecimals(testFloat, 9));
System.out.println(testFloat + "");
System.out.println(getFloatAsStringIWithNDecimals(testFloat, 3));
System.out.println(getFloatWithNDecimals(testFloat, 3));
System.out.println(Float.parseFloat(getFloatAsStringIWithNDecimals(testFloat, 3)));
System.out.println(getFloatAsStringIWithNDecimals(Float.parseFloat(getFloatAsStringIWithNDecimals(testFloat, 3)), 9));
System.out.println(getFloatWithNDecimals(Float.parseFloat(getFloatAsStringIWithNDecimals(testFloat, 3)), 3));
System.out.println(getFloatAsStringIWithNDecimals(getFloatWithNDecimals(Float.parseFloat(getFloatAsStringIWithNDecimals(testFloat, 3)), 2), 9));
public static String getFloatAsStringIWithNDecimals(float f, int nDecimals) {
return String.format(Locale.US, "%.0"+nDecimals+"f", f);
}
public static float getFloatWithNDecimals(float f, int nDecimals) {
int multiplier = 1;
for(int i = 1; i <= nDecimals; i++) {
multiplier *= 10;
}
return ( (int)(f * multiplier) ) *1f / multiplier;
}
public static float getFloatWithNDecimals(float f) {
return getFloatWithNDecimals(f, 2);
}
这是输出:
-18.479999542
-18.48
-18.480
-18.48
-18.48
-18.479999542
-18.48
-18.479999542
为什么这些输出,即使我截断小数,将它们转换为 int,然后除以 100,即使我通过使用
String.format
将浮点数转换为 String 来截断它们?
谢谢
我只需要在变量中存储一个带有n位小数的浮点数,但它看起来太复杂了......
这不是
float
可以存储的内容。您的问题类似于:“我只想将“hello”一词存储在 int
变量中,但看起来太复杂了”。或者“我想将高尔夫球杆放入保龄球包中,但它不合适”。
浮点数以固定精度存储数字,但以二进制形式存储。
拿一张纸。去写1/20吧。没问题。 “0.05”。现在做同样的事情并在上面写上1/15。 0.066666666666666...哦天哪。你做不到。没有一张信纸足够大,无法准确地写下来。
1/20
有效而
1/15
无效的原因是你以10为基数编写它,而10的素因数是5和2。如果你的分数的分母仅分解为素因数5和2,那么它就会“起作用”(20 是
2*2*5
- 这就是五和二,所以可以起作用),如果其中甚至有一个非 5、非 2 值,那么它就不起作用; 15 是
5*3
- 那里有一个 3,所以它不起作用。在二进制中,唯一的约数是
2。所以分母需要分解成任意数量的 2。
这是另一个简单的示例,您确实应该编写并运行它,以便充分理解以下概念:float
和
double
本质上是不精确的,永远不能舍入为浮点数或双精度数,如果您尝试,您将失败了。
double v = 0.0;
for (int i = 0; i < 10; i++) v+= .1;
System.out.println(v == 1.0);
这将打印false
,这是非常令人惊讶的。从什么时候开始十倍哦点一不等于一了?但事实并非如此,因为 10 作为分母是
5*2
,其中包含一个非 2,所以虽然十分之一在十进制表示法中可以很好地工作 (
0.1
),但在二进制表示法中却不行 - 它就像
0.3333333....
。
round(1/3 to 10 decimal places) * 3
的结果不是1.0(是
0.9999999999
),
round(1/10 to 10 binarial places) * 10
的结果也不是1.0。现在尝试相同的特技,但将
0.1
替换为
0.125
(这是八分之一,这确实有效:8 分解为
2*2*2
- 这都是二,所以在浮点/双精度数学中是精确的),确实八乘以八分之一确实等于 1.0。既然您知道了,您就知道“四舍五入到 8 位”是对
float
/
double
的无意义操作。他们根本做不到 - 因为你的意思是“8 位十进制表示法”。 那我该怎么办?
更好的选择是在渲染时进行四舍五入
- 每当该数字转换为字符串时,对然后进行四舍五入。例如:
double v = 5;
v = v / 3.0;
System.out.printf(".4f", v);
在打印操作期间将您的数字四舍五入为 1.6667,效果非常好。如果您不想将此字符串直接发送到标准输出,则可以使用
String.format
。任何指定“返回四舍五入的数值”的方法都不能将 double
或
float
作为返回类型。它需要有 BigDecimal
或
String
。
或者,如果您有一个设定的固定点,您想要对其进行操作,例如“我想存储欧分,但不想存储半分,所以总是四舍五入到两位小数”,然后存储最小单位在
long
。因此,不要为 1 欧元和 23 欧分的东西存储
double priceOfThisThing = 1.23;
。而是存储 long priceInCents = 123;
。
旁注:
float
没用
double
更快、更好、更强。没有理由使用 float ,除非 [A] 你要创建一个非常大的数组(超过 ~100k 个元素!),或者 [B] 某些规范明确要求 IEEE32
float
是的,
double
是“更大”。您可能会认为这意味着它们“较慢”。除非您拥有“真正”的奇特硬件,否则这是不正确的;芯片在其设计运行的尺寸上运行速度更快。对于现代芯片(不到十年前制造的任何芯片)来说,这是
double
,而不是 float
。换句话说,
float
正确的几率实际上为零。