ZetCode

Java 浮点数

最后修改日期:2025 年 4 月 1 日

Java 提供了两种浮点类型:float (32 位) 和 double (64 位),两者都基于 IEEE 754 标准。本指南将探讨它们的特性、精度细微差别以及数值任务的最佳实践。

Java Float 和 Double 概述

本节探讨 Java 的两种浮点类型:floatdouble。两者都遵循 IEEE 754 标准,但在大小和精度上有所不同,适用于不同的用例。掌握它们的范围和限制是明智选择的关键。下面的示例通过示例值突出了它们的属性。这种见解有助于编写高效的 Java 程序。

FloatOverview.java
void main() {

    // Float and double declarations
    float simpleFloat = 3.14159f;
    double preciseDouble = 3.141592653589793;
    
    System.out.println("Float: " + simpleFloat);
    System.out.println("Double: " + preciseDouble);
    
    // Range constants
    System.out.println("\nFloat MIN: " + Float.MIN_VALUE);
    System.out.println("Float MAX: " + Float.MAX_VALUE);
    System.out.println("Double MIN: " + Double.MIN_VALUE);
    System.out.println("Double MAX: " + Double.MAX_VALUE);
    
    // Size in bytes
    System.out.println("\nFloat size: " + Float.BYTES + " bytes");
    System.out.println("Double size: " + Double.BYTES + " bytes");

    // Additional example: Exponential notation
    double expDouble = 2.5e-12;
    System.out.println("\nExponential double: " + expDouble);
}

Java 的 float 类型是 32 位 IEEE 754 单精度数字,具有 6-7 位有效数字,范围为 ±1.4×10⁻⁴⁵ 到 ±3.4×10³⁸,以 'f' 后缀标记。 double 类型是 64 位双精度变体,提供 15-16 位数字,范围为 ±4.9×10⁻³²⁴ 到 ±1.7×10³⁰⁸,是 Java 中小数的默认类型。

这些类型分别存储在 4 字节和 8 字节中,满足不同的需求 - float 用于内存效率,double 用于精度。指数表示法(例如,2.5e-12)适用于两者,简化了处理极值的工作。对于内存受限的情况,选择 float;对于精度至关重要的任务,选择 double。

精度与舍入

由于二进制存储,Java 的浮点类型表现出精度怪癖。本节揭示了小数位如何未对齐以及如何纠正它们。示例展示了 Java 的舍入工具,用于实际控制。了解这些行为可以防止计算中出现意外结果。这对于 Java 中可靠的数值处理至关重要。

PrecisionExamples.java
void main() {

    // Precision issue
    double x = 0.3;
    double y = 0.6;
    double result = x + y;
    
    System.out.println("0.3 + 0.6 = " + result);
    System.out.println("0.3 + 0.6 == 0.9? " + (result == 0.9));
    System.out.printf("Full precision: %.17f%n", result);
    
    // Rounding examples
    double num = 5.6789;
    System.out.println("\nRounding examples:");
    System.out.println("Round " + num + ": " + Math.round(num));
    System.out.println("Floor " + num + ": " + Math.floor(num));
    System.out.println("Ceil " + num + ": " + Math.ceil(num));
    
    // Additional example: Formatting
    System.out.printf("Formatted to 2 decimals: %.2f%n", num);
}

Java 的 float 和 double 类型基于 IEEE 754,难以进行精确的小数表示,因此 0.3 + 0.6 变为 0.8999999999999999,而不是 0.9。这源于二进制近似,误差可能会在迭代数学中累积,需要注意。

Java 提供了 Math.round 用于最接近的整数,Math.floor 向下截断,以及 Math.ceil 向上舍入,所有这些都可以有效地调整 double 值。例如,Math.round(5.6789) 产生 6,而 printf 与 %.2f 格式化为 5.68 以供显示。这些工具帮助管理精度,但开发人员必须预料到设计中的这种偏差。

比较浮点值

由于精度漂移,在 Java 中比较 float 或 double 需要技巧。简单的相等性测试常常会误导,因此本节提供了一种更安全的策略。它还在 Java 中以独特的方式处理特殊值,如无穷大和 NaN。该示例通过清晰的结果演示了这些技术。这确保了 Java 代码库中值得信赖的比较。

FloatComparison.java
boolean nearlyEqual(double a, double b, double epsilon) {

    double diff = Math.abs(a - b);
    
    if (a == b) return true;
    
    if (a == 0 || b == 0 || diff < Double.MIN_VALUE) {
        return diff < (epsilon * Double.MIN_VALUE);
    }

    return diff / (Math.abs(a) + Math.abs(b)) < epsilon;
}

void main() {
    double a = 0.3 + 0.6;
    double b = 0.9;
    
    System.out.println("Direct equality: " + (a == b));
    System.out.println("NearlyEqual: " + nearlyEqual(a, b, 1e-10));
    System.out.printf("Difference: %.17f%n", a - b);
    
    // Special values
    double nan = Double.NaN;
    double inf = Double.POSITIVE_INFINITY;
    
    System.out.println("\nNaN == NaN: " + (nan == nan));
    System.out.println("Double.isNaN(nan): " + Double.isNaN(nan));
    System.out.println("INF == INF: " + (inf == inf));
    
    // Additional example: Tiny value check
    double tiny = 1e-14;
    System.out.println("\nNearlyEqual(" + tiny + ", 0): " + nearlyEqual(tiny, 0, 1e-10));
}

在 Java 中,使用 == 比较 float 或 double 是不可靠的,因为由于微小的二进制误差,0.3 + 0.6 != 0.9,通常相差 1.1e-16 这样的分数。 nearlyEqual 方法使用 epsilon (例如,1e-10) 来评估接近度,考虑到 Double.MIN_VALUE 对于微小数字,提供了一个可靠的替代方案。

特殊情况,如 Double.NaN(永远不等于自身,使用 isNaN 检查)和 Double.POSITIVE_INFINITY(等于自身)需要不同的处理以确保安全。对于像 1e-14 这样的接近零的值,nearlyEqual== 失效的地方表现出色,确保比较中的精度。此方法与 Java 的严格类型匹配,以获得可靠的结果。

用于财务计算的浮点数

Java 的 float 和 double 类型在需要精确小数的财务上下文中会出错。本节将它们的缺陷与基于整数的精度修正方法进行了对比。示例说明了 Java 中的利息计算和货币舍入。这些方法保证了货币应用的精度。对于强大的解决方案,建议改用 Java 的 BigDecimal。

FinancialExamples.java
void main() {

    double principal = 2000.00;
    double rate = 0.04; // 4%
    int years = 8;
    
    // Floating-point calculation
    double futureValueFloat = principal * Math.pow(1 + rate, years);
    System.out.printf("Future value (float): $%.2f%n", futureValueFloat);
    
    // Integer cents for accuracy
    long principalCents = 200_000; // $2000.00 in cents
    long futureValueCents = Math.round(principalCents * Math.pow(1 + rate, years));
    double futureValue = futureValueCents / 100.0;
    System.out.printf("Accurate future value: $%.2f%n", futureValue);
    
    // Rounding example
    double amount = 456.7891;
    System.out.println("\nAmount to cents: " + Math.round(amount * 100) / 100.0);
    
    // Additional example: Discount calculation
    double price = 29.95;
    double discount = 0.15; // 15%
    double savings = Math.round(price * discount * 100) / 100.0;
    System.out.printf("Discount on $%.2f: $%.2f%n", price, savings);
}

Java 的 float 和 double 类型在财务数学中引入了轻微的误差,例如 2000 美元以 4% 的利率计算 8 年的利息,这是由于二进制舍入怪癖造成的。使用美分作为 long(例如,200_000 代表 2000 美元)并在计算后进行转换可提供精确的 2738.03 美元,从而避免了 float 问题。

使用 Math.round 对美分进行舍入(例如,将 45678 美分舍入到 456.78 美元)可确保精确的货币价值,这对于财政完整性至关重要。这种方法在 29.95 美元的 15% 折扣时产生完全 4.49 美元,避免了直接使用 double 时的漂移。为了获得最终精度,请导入 java.math.BigDecimal 以处理没有二进制近似的小数。

最佳实践

在 Java 中,对于大多数任务,请优先使用 double 而不是 float,仅在内存紧张的情况下保留 float。对于财务精度,请使用 BigDecimal 或美分,避免直接使用 float/double。跳过 == 进行比较,使用带有 epsilon 的 nearlyEqual 确保可靠性。慎重利用 Math.roundfloorceil,并使用 isNaN/isInfinite 检查 NaN/infinity。这些步骤确保 Java 数值运算既精确又高效。

资料来源

从以下资源了解更多信息:Java Float 文档Java Double 文档,以及 BigDecimal 文档

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有Java教程