如何在 Java 中截断小数(int 强制转换、Math.floor、BigDecimal setScale)

.## 1. 在 Java 中“截断小数”是什么意思?

当人们在 Java 中说“截断小数”时,通常指的是去掉小数部分以得到整数在第 n 位小数后舍弃数字
在实际代码中,“截断”可能有不止一种含义,所以我们先澄清结论。

目次

1.1 “截断”是向下取整(floor)还是强制类型转换(cast)?(术语)

主要有两种“截断”模式。

  • (A)向 0 方向截断(truncate) 去除小数部分,但对负数来说是“更接近零”。示例:3.9 → 3-3.9 → -3 → 在 Java 中,(int) 强制类型转换 的行为就是这样
  • (B)向 −∞ 方向截断(floor) 在数轴上向下(更小的值)移动 示例:3.9 → 3-3.9 → -4 → 在 Java 中,Math.floor() 的行为就是这样

初学者常常感到困惑,因为对正数而言,(A) 和 (B) 产生相同的结果。但一旦处理负数,结果就会分歧

1.2 本文帮助你实现的目标(按使用场景的最快路径)

搜索“java truncate decimals”的人通常想要以下其中一种。

  • 仅仅转换为整数(例如 3.14 → 3) → 使用 (int) 强制类型转换是最快的。但要注意负数。
  • 在两位小数处截断(例如 1.239 → 1.23)Math.floor(x * 100) / 100 可以工作 → 但如果精度重要,使用 BigDecimal 更安全
  • 对金钱/计费/费率等不能出错的场景 → 使用 BigDecimal + setScale + RoundingMode

如果选择了错误的方法,往往“看似可行”但随后会出错。特别是,基于 double 的计算可能引入精度误差,因此在涉及金钱的逻辑时,最好从一开始就使用 BigDecimal 设计。

1.3 常见陷阱(先解决这些)

常见的三大错误:

  • 假设 (int) 总是向下取整(floor) → 实际上它是向零方向取整,所以负数会不同。
  • 使用 Math.floor 来截断小数,却出现轻微不匹配 → 通常是 double 精度导致的(取决于数值/环境)。
  • 把格式化(DecimalFormat 等)当作会改变计算值 → 只会改变显示,内部值保持不变。

首先确认最简路径:“如果只需要整数,直接强制类型转换”。然后了解“负数陷阱”。

在下一节中,我们将通过具体示例解释使用 (int) 强制类型转换进行截断

2. 如果你只想要 int,强制类型转换可以(但有陷阱)

如果你只想去掉小数部分并得到整数 (int),在 Java 中最快的方法是强制类型转换(类型转换)。但强制类型转换并不是“数学截断(floor)”。它是向零方向取整(truncate),这点非常重要。

2.1 基础:使用 (int) 去除小数部分

当你将 doublefloat 转换为 int 时,小数部分会被舍弃。

double x = 3.99;
int a = (int) x;
System.out.println(a); // 3

这是针对常见意图“我想截断小数”的最简答案。

步骤如下:

  • 确认该值是 double / float
  • 在前面加 (int) 将其强制转换为 int
  • 验证结果——包括可能出现的负数

2.2 重要提示:强制类型转换向零方向取整(负数不同)

这是初学者的第一大陷阱。
强制类型转换并不总是“向更小的值舍入”。

System.out.println((int) 3.9);   // 3
System.out.println((int) -3.9);  // -3

-3.9 变成 -3(而不是 -4)是因为强制类型转换的方向是更接近零

  • 3.9 → 3(向零方向)
  • -3.9 → -3(向零方向)

如果你想要“数学截断(floor)”,需要使用 Math.floor(),而不是强制类型转换。
如果忽视这一点,可能在 盈亏计算、费用计算或坐标运算 等场景中出现 bug,因为 只有负数那一侧会出错

2.3 常见错误:假设四舍五入 / 在负数上出错

类型转换不是四舍五入。
如果你认为“0.9 变成 1”,你就会出错。

System.out.println((int) 0.9);  // 0
System.out.println((int) 1.9);  // 1

此外,对于负数,它可能会感觉值“增加了”或“减少了”出乎意料。

  • -3.9 → -3 在数轴上向更大的方向移动
  • 如果你期望 -4 ,这是一个真正的 bug

修复 很简单:

  • 如果你只处理正值 → 类型转换就可以
  • 如果可能出现负值 → 考虑 Math.floor()
  • 如果需要严格精度(金钱等) → 考虑 BigDecimal

在下一节,我们将解释 Math.floor() / Math.ceil(),它允许你即使对于负数也以数学一致的方式截断。

3. 使用 Math.floor / Math.ceil 进行正确的十进制截断

如果你想数学正确地截断小数,基本方法是使用 Math.floor() 而不是类型转换。
特别是在可能出现负数的情况下,简单选择 Math.floor() 就能显著减少 bug。

3.1 使用 Math.floor() 进行截断(向下取整函数)

Math.floor() 被称为向下取整函数,它在数轴上将值向下取整(向较小值方向)

System.out.println(Math.floor(3.9));   // 3.0
System.out.println(Math.floor(-3.9));  // -4.0

关键点:

  • 3.9 → 3.0 (向下移动)
  • -3.9 → -4.0 (进一步向下移动)

最大的优势是截断在负数上不会出现意外行为

一个常见的陷阱是 Math.floor() 返回 double 类型
如果你需要整数,必须在之后转换它。

3.2 Math.ceil() 向上取整 — 不要混淆它

Math.ceil()Math.floor() 的反向操作,它在数轴上向上取整(向较大值方向)

System.out.println(Math.ceil(3.1));   // 4.0
System.out.println(Math.ceil(-3.1));  // -3.0

一个常见的错误是:

  • 认为“移除小数”意味着 ceil → 实际上,ceil 是向上取整,这往往与你想要的相反。

如果你搜索“截断小数”,floor 通常是正确选择。

3.3 转换为 long / int 时的类型转换

如果你想将 Math.floor() 的结果转换为 intlong,你需要对其进行类型转换。

double x = -3.9;

int a = (int) Math.floor(x);
System.out.println(a); // -4

注意点:

  • Math.floor() 的结果是 double
  • 转换为 int 会移除小数部分(此时没有不匹配)
  • 如果值太大,可能无法放入 int

为了安全,你可以先选择 long

double x = 12345678901.9;

long b = (long) Math.floor(x);
System.out.println(b); // 12345678901

常见的错误包括:

  • 忘记类型转换,让值保持为 double
  • 不理解 floor 和类型转换的区别,导致负数上的 bug
  • 超出 int 范围时的溢出

简而言之,如果可能涉及负数,从一开始就围绕 Math.floor() 设计是最安全的。

在下一节,我们将用具体示例解释如何在第 n 位小数处截断(例如,保留两位小数)。

4. 如何在第 n 位小数处截断(显示和计算中常见)

在实际应用中,非常常见的需求是保留到特定小数位并丢弃其余部分,而不仅仅是移除所有小数。
示例包括税率、汇率、单价和百分比。

这里我们整理了在第 n 位小数处截断的实用方法。
在使用 double 时可能出现精度问题,因此我们也会涵盖陷阱。

4.1 基本模式:乘以 10^n,向下取整,然后除以

最常见的模式是:

  1. 决定小数位数 n
  2. 乘以 10^n
  3. 使用 Math.floor()
  4. 除以 10^n 恢复原始尺度

将数值截断到 2 位小数(例如,1.239 → 1.23):

double x = 1.239;
double y = Math.floor(x * 100) / 100;
System.out.println(y); // 1.23

将数值截断到 3 位小数(例如,1.2399 → 1.239):

double x = 1.2399;
double y = Math.floor(x * 1000) / 1000;
System.out.println(y); // 1.239

此方法的优势:

  • 代码简短且易于理解
  • 使用 Math.floor(),对负数的行为保持一致
  • 不需要额外的库

然而,由于该方法依赖于 double不适用于对精度要求严格的计算(如金钱)

4.2 陷阱:由于 double 精度导致的意外结果

Java 的 double 是一种 浮点类型,无法始终精确表示诸如 0.1 或 0.01 之类的值。
因此,你可能会看到看似“荒唐”的差异(取决于具体数值和运行环境)。

  • 你期望得到 1.29,但实际显示为 1.28
  • 你处理 0.3,但内部实际是 0.299999999…

例如,下面的代码在逻辑上是正确的:

double x = 1.29;
double y = Math.floor(x * 100) / 100;
System.out.println(y);

但内部,x * 100 可能会被表示为 128.999999999…
如果 floor 返回 128,结果就会变成 1.28
这不是 Java 的 bug,而是 浮点运算的属性

更棘手的是,这种情况 仅偶尔出现,因此在测试时不易捕捉。

4.3 使用 BigDecimal 安全截断(推荐)

如果你希望在第 n 位小数处 可靠 地截断,使用 BigDecimal 是最安全的选择。
BigDecimal 能够 精确表示十进制数,并且在金钱计算中被广泛使用。

基本模式如下:

import java.math.BigDecimal;
import java.math.RoundingMode;

BigDecimal x = new BigDecimal("1.239");
BigDecimal y = x.setScale(2, RoundingMode.DOWN); // truncate at 2 decimal places
System.out.println(y); // 1.23

关键要点:

  • setScale(2, …) 保留两位小数
  • RoundingMode.DOWN 指定向零截断

需要注意的是,BigDecimal 的 DOWNMath.floor() 并不相同。
在负数情况下,两者的行为会有差异,因此你可能还需要考虑 FLOOR(后文会解释)。

常见错误包括:

  • 从 double 创建 BigDecimal(会引入精度误差)
  • 忘记调用 setScale
  • 使用错误的舍入模式,导致意外的四舍五入

总之,Math.floor 可能足以满足仅用于显示的场景,但在 重要计算 中,BigDecimal 是最安全的选择。

在下一节中,我们将深入探讨 BigDecimal 的截断方式,包括 在实际代码中创建实例的正确方法

5. 使用 BigDecimal 截断(金融、计费和费率的最安全方案)

如果你希望以 最高精度 截断小数,BigDecimal 应该是 Java 中的首选。
double 虽然速度快,但容易出现精度错误,这在金钱、计费、费用或费率计算中常常导致问题。
BigDecimal 以更接近人类直觉的方式处理 十进制数,因此在业务代码中被广泛采用。

5.1 BigDecimal 基础:从 double 创建是危险的(最重要)

使用 BigDecimal 时最常见的错误是 创建方式
如果直接传入 double,内部的精度误差会被保留下来。

import java.math.BigDecimal;

BigDecimal a = new BigDecimal(0.1);
System.out.println(a); // May become something like 0.10000000000000000555...

对该值进行截断或计算往往会导致“神秘”的差异。

安全的创建 BigDecimal 方法有:

(A)从字符串创建(最明确且安全)

BigDecimal a = new BigDecimal("0.1");

(B)使用 BigDecimal.valueOf(double)(通常推荐)

BigDecimal a = BigDecimal.valueOf(0.1);

对于初学者,最安全的规则是:有疑问时,从字符串创建 BigDecimal

5.2 使用 RoundingMode.DOWN 在第 n 位小数处截断

要在第 n 位小数处截断,使用 setScale

  • setScale(2, ...) → 保留两位小数
  • RoundingMode.DOWN → 向零方向截断
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    BigDecimal x = new BigDecimal("123.4567");
    BigDecimal y = x.setScale(2, RoundingMode.DOWN);
    
    System.out.println(y); // 123.45
    

如果你想去掉所有小数并得到类似整数的值,同样的方法适用。

BigDecimal x = new BigDecimal("123.999");
BigDecimal y = x.setScale(0, RoundingMode.DOWN);

System.out.println(y); // 123

一个重要细节:setScale 会改变数值本身,而不仅仅是显示方式。这是一次计算操作,而不是格式化。

5.3 向零截断 vs 向下取整(重要区别)

BigDecimal 提供多种舍入模式,其中有两种看起来像“截断”,容易引起混淆。

  • RoundingMode.DOWN : 向零截断
    • 3.9 → 3
    • -3.9 → -3
  • RoundingMode.FLOOR : 向负无穷截断(数学取整)
    • 3.9 → 3
    • -3.9 → -4

如果你希望数值始终向数轴的较小方向移动,FLOOR 是正确的选择。

import java.math.BigDecimal;
import java.math.RoundingMode;

BigDecimal x1 = new BigDecimal("-3.9");
System.out.println(x1.setScale(0, RoundingMode.DOWN));  // -3
System.out.println(x1.setScale(0, RoundingMode.FLOOR)); // -4

哪一种“正确”完全取决于你的使用场景。

  • 仅仅去掉数字(截断) → DOWN
  • 数学意义上的截断(向下取整) → FLOOR

如果这个区别不清晰,负数可能导致严重的规格错误。

5.4 常见的 BigDecimal 错误

以下是在使用 BigDecimal 截断时常见的错误:

  • 从 double 创建 BigDecimal 并引入精度误差 → 避免 new BigDecimal(0.1);使用字符串或 valueOf
  • 使用错误的尺度 → 例如,使用 0 而不是 2 位小数
  • 混淆舍入模式 → 把 DOWN 和 FLOOR 搞混,导致负数问题
  • 在只想格式化时却改变了数值setScale 会改变数值;应将计算与显示分离

简而言之,对于金钱、账单或费率——即使一分钱的差异也很重要——应使用 BigDecimal 并 同时修正创建方式和舍入模式

在下一节中,我们将说明如何避免混淆 显示格式化计算截断

6. DecimalFormat / String.format 仅用于显示(不要用于计算)

在截断小数时,关键是要区分是想 截断计算得到的数值 还是 仅仅格式化显示。将两者混合会导致 屏幕显示正确但内部数值错误 的情况,进而产生后续 bug。

关键要点:DecimalFormatString.format 仅用于显示。如果需要截断实际数值,请使用 BigDecimalMath.floor()

6.1 使用 DecimalFormat 格式化小数位(可实现截断)

DecimalFormat 是一个将数字转换为格式化字符串的类。例如,当你想显示两位小数时常会使用它。

import java.text.DecimalFormat;

double x = 1.239;

DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(x)); // May become 1.24 (rounded)

重要的是,format() 的返回值是一个 String,而变量 x 本身的数值并未改变。

你也可以指定舍入模式。若要截断显示,可按如下操作:

import java.text.DecimalFormat;
import java.math.RoundingMode;

double x = 1.239;

DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.DOWN);

System.out.println(df.format(x)); // 1.23

However, this only changes the displayed string; the value of x remains unchanged.

6.2 Caveats When Using String.format

String.format also lets you control numeric output formatting.

double x = 1.239;
System.out.println(String.format("%.2f", x)); // Often rounds to 1.24

In many cases, String.format("%.2f", x) performs round half up behavior.
It’s usually unsuitable if you specifically want truncation.

A very common beginner mistake is converting a formatted string back into a number and using it in calculations.

double x = 1.239;
String s = String.format("%.2f", x); // "1.24"
double y = Double.parseDouble(s);    // 1.24

// At this point, calculations use a rounded value, not a truncated one

This mixes display concerns into calculation logic, making future changes harder.

6.3 Key Pitfall: Correct Display Does Not Mean Correct Values

The most dangerous assumption is “it looks correct on the screen, so it must be fine.”

  • DecimalFormat / String.format change how values look
  • Math.floor / BigDecimal.setScale change the actual value

If you confuse these, you may run into problems like:

  • The UI shows “$1.23” but internal calculations still sum “$1.239”
  • Rounding happens at the wrong stage, causing billing totals to differ
  • Display rounding leaks into calculations, causing audit discrepancies

A simple rule of thumb:

  • Only formatting for display → DecimalFormat (or UI formatting)
  • Truncating as part of calculation → BigDecimal / Math.floor

In the next section, we’ll provide a quick reference table so you can instantly choose the right approach by use case.

7. Best Practices by Use Case (Quick Reference)

“Java decimal truncation” is confusing because there are multiple valid approaches.
Here, we fix the decision by mapping use case → best solution, so you can choose quickly.

7.1 Convert to int (Positive Numbers Only) → Casting Is Enough

If you just want to remove decimals and convert to int, and negative numbers will never appear, casting is the fastest option.

double x = 12.99;
int y = (int) x;
System.out.println(y); // 12

Things to remember:

  • Casting rounds toward zero
  • With positive values, it looks the same as truncation
  • If negatives may appear, don’t lock in this approach

7.2 Truncate Including Negative Numbers → Math.floor

If you want mathematical truncation (floor), Math.floor() is the correct choice.
It’s safer than casting when negative numbers may occur.

double x = -3.9;
int y = (int) Math.floor(x);
System.out.println(y); // -4

Common notes:

  • Math.floor() returns a double
  • Cast to int or long if needed
  • Watch out for int overflow with large values

7.3 Truncate at the n-th Decimal Place (Strict) → BigDecimal + setScale

If you want to truncate at a specific decimal place without precision errors, BigDecimal is the safest option.

import java.math.BigDecimal;
import java.math.RoundingMode;

BigDecimal x = new BigDecimal("1.239");
BigDecimal y = x.setScale(2, RoundingMode.DOWN);

System.out.println(y); // 1.23

This approach is ideal for:

  • Money (JPY, USD, etc.)
  • Billing and tax calculations
  • Rate calculations where errors are unacceptable
  • Batch jobs requiring deterministic results

Common pitfalls:

  • Do not create BigDecimal from double (avoid new BigDecimal(0.1) )
  • Understand the difference between DOWN and FLOOR (negative numbers)

7.4 Format for Display Only → DecimalFormat (Never for Calculation)

If you only want to format numbers for display (e.g., two decimals), DecimalFormat is convenient.
However, it does not change the underlying numeric value, so never use it for calculations.

import java.text.DecimalFormat;
import java.math.RoundingMode;

double x = 1.239;

DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.DOWN);

System.out.println(df.format(x)); // "1.23"

注意:

  • 返回值是 String
  • 将显示逻辑混入计算会导致错误

一句话总结要点是:

  • 快速且简单 → 强制转换
  • 负数情况下正确 → Math.floor
  • 优先准确(实际场景) → BigDecimal
  • 仅用于显示 → DecimalFormat

接下来,我们将总结最常见的错误以及如何避免它们。

8. 常见错误与关键警告

小数截断常常“看起来有效”,这使得初学者容易忽视细微的错误。以下是实际场景中的错误以及如何避免它们。

8.1 误解“截断”的含义(向 0 取整 vs 向下取整)

最常见的错误是假设 截断总是意味着“变小”。 在 Java 中,其含义取决于使用的方法。

  • 强制转换 (int) : 向零方向

    • 3.9 → 3
    • -3.9 → -3
    • Math.floor() : 向负无穷方向

    • 3.9 → 3
    • -3.9 → -4

如果可能出现负值且使用强制转换,则只有负数方向会出错。如果测试仅使用正数,这种情况常常不被注意到。

对策:

  • 如果可能出现负数,建议从一开始就使用 Math.floor()
  • 使用 BigDecimal 时,勿混淆 RoundingMode.DOWNFLOOR

8.2 由于 double 精度导致的意外结果

在使用 Math.floor(x * 100) / 100 实现截断时,即使逻辑看起来正确,也可能出现不匹配。原因在于 double 的内部表示。

double x = 1.29;
double y = Math.floor(x * 100) / 100;
System.out.println(y);

根据具体数值,x * 100 可能变成 128.999999999...,导致结果为 1.28。由于并非每次都会出现,这类问题常在生产环境中被发现。

对策:

  • 在金钱和计费场景使用 BigDecimal
  • 若使用 double,则仅限于对小误差可接受的情况

8.3 在金钱计算中使用 double

在金钱计算中使用 double 常导致:

  • 截断后总额不匹配
  • 小的四舍五入误差累积
  • 显示值与内部值不一致

对策:

  • 对货币值使用 BigDecimal
  • 修正创建方式(使用 String 或 valueOf
  • 明确记录四舍五入规则

8.4 将显示格式用于计算

使用 DecimalFormat 或 String.format 进行格式化后再解析回数字是危险的。

double x = 1.239;
String s = String.format("%.2f", x); // display formatting
double y = Double.parseDouble(s);    // used in calculation (dangerous)

这会导致:

  • 显示时的四舍五入泄漏到计算中
  • 规范变更影响逻辑
  • 失去显示与计算的分离

对策:

  • 使用 BigDecimalMath.floor() 完成计算
  • 仅在最终显示阶段进行格式化
  • 保持计算与展示分离

9. 总结:复制粘贴结论

在 Java 中截断小数有多种方式,但最快的路径是 根据使用场景选择。以下是可直接使用的结论。

9.1 如果不确定,使用以下方法

案例 1:转换为 int(仅正数)

double x = 12.99;
int y = (int) x;  // 12

注意:对于负数,-3.9 → -3,因此这不是数学意义上的向下取整。

案例 2:包括负数的数学截断

double x = -3.9;
int y = (int) Math.floor(x); // -4

要点:Math.floor() 向负无穷方向截断。

案例 3:在 n 位小数处截断(double,简易)

*仅在可接受小精度误差的情况下使用。

double x = 1.239;
double y = Math.floor(x * 100) / 100; // 1.23

警告:可能出现 double 精度问题。若需严格准确,请使用 BigDecimal。

案例 4:可靠地在 n 位小数处截断(推荐)

import java.math.BigDecimal;
import java.math.RoundingMode;

BigDecimal x = new BigDecimal("1.239");
BigDecimal y = x.setScale(2, RoundingMode.DOWN); // 1.23

关键点:

  • 不要从 double 创建 BigDecimal
  • 在金钱、计费和费率等场景中使用此方法

案例 5:仅用于显示(绝不用于计算)

import java.text.DecimalFormat;
import java.math.RoundingMode;

double x = 1.239;

DecimalFormat df = new DecimalFormat("0.00");
df.setRoundingMode(RoundingMode.DOWN);

System.out.println(df.format(x)); // "1.23"

要点:这会产生一个 String;数值本身保持不变。

常见问题

Q1. 在 Java 中截断小数的最简方法是什么?

答案: 如果只需要整数,使用 (int) 强制转换是最快的。如果可能出现负数,考虑使用 Math.floor()

Q2. 为什么对负数使用 (int) 会得到意外的结果?

答案: 强制转换会向零方向取整。例如,-3.9 → -3。若需要数学意义上的截断,请使用 Math.floor()

Q3. 如何截断到两位小数(例如,1.239 → 1.23)?

答案: 一种简单的方法是使用 Math.floor(x * 100) / 100。若要求严格的精度,请使用 BigDecimal.setScale(2, RoundingMode.DOWN)

Q4. 为什么 Math.floor(x * 100) / 100 有时会产生错误的值?

答案: 这是由于 double 精度误差导致的。对于金钱或计费等场景,请改用 BigDecimal。

Q5. 使用 BigDecimal 正确的截断方式是什么?

答案: 应从字符串或 valueOf 创建 BigDecimal,然后使用 setScale(n, RoundingMode.DOWN)。避免使用 new BigDecimal(double)

Q6. RoundingMode.DOWN 与 FLOOR 有何区别?

答案: 它们在负数时表现不同。DOWN 向零截断;FLOOR 向负无穷截断。根据是需要截断还是数学意义上的向下取整来选择。

Q7. 能否使用 DecimalFormat 截断小数?

答案: 可以通过设置舍入模式来截断显示,但这仅影响显示,内部数值并未改变。

Q8. 何时在计算中应用截断?

答案: 这取决于具体规范。需要决定是每一步都截断还是在最后截断,明确记录规则并保持一致实现——最好使用 BigDecimal。