Keywords: JavaScript | Java | JSON | precision loss | long integer
Abstract: This article examines the precision loss problem that occurs when transferring Java long integer data to JavaScript, stemming from differences in numeric representation between the two languages. Java uses 64-bit signed integers (long), while JavaScript employs 64-bit double-precision floating-point numbers (IEEE 754 standard), with a mantissa of approximately 53 bits, making it incapable of precisely representing all Java long values. Through a concrete case study, the article demonstrates how numerical values may have their last digits replaced with zeros when received by JavaScript from a server returning Long types. It analyzes the root causes and proposes multiple solutions, including string transmission, BigInt type (ES2020+), third-party big number libraries, and custom serialization strategies. Additionally, the article discusses configuring Jackson serializers in the Spring framework to automatically convert Long types to strings, thereby avoiding precision loss. By comparing the pros and cons of different approaches, it provides guidance for developers to choose appropriate methods based on specific scenarios.
Background and Phenomenon Analysis
In modern web development, the separation of front-end and back-end architectures has become increasingly common, with Java often serving as a backend language interacting with frontend JavaScript. JSON, as a universal data interchange format, plays a crucial role in this process. However, when long integer data (java.lang.Long) from Java is transmitted via JSON to the JavaScript environment, developers may encounter unexpected precision issues.
Case Reproduction and Problem Diagnosis
Consider a typical scenario: a Spring backend controller returns a Long value, such as 793548328091516928L. After initiating a GET request via jQuery's $.get() method, printing the received data in the JavaScript console shows 793548328091516900—the last two digits are replaced with zeros. Notably, when accessing the same endpoint directly via the browser address bar, the value displays correctly, indicating that the root cause lies in JavaScript's number handling mechanism.
Root Cause: Differences in Numeric Representation
Java's long type is a 64-bit signed integer, with a range from -2^63 to 2^63-1, capable of precisely representing all integer values within this range. In contrast, JavaScript has only one numeric type—a 64-bit double-precision floating-point number based on the IEEE 754 standard. This representation consists of 1 sign bit, 11 exponent bits, and 52 mantissa bits (effectively 53 bits, including an implicit 1). Consequently, the range of integers JavaScript can represent precisely is limited to -2^53 to 2^53 (i.e., -9007199254740991 to 9007199254740991).
For integers outside this range, such as 793548328091516928 in the case (greater than 2^53), JavaScript cannot represent them exactly, leading to precision loss. Specifically, when converting such a number to a floating-point value, the mantissa is insufficient to accommodate all significant digits, causing the least significant bits to be rounded, manifesting as trailing digits becoming zeros.
Solution Exploration
To address this issue, developers can adopt various strategies to ensure data precision.
Solution 1: String Transmission
The most straightforward approach is to transmit Long-type data as strings. On the backend, this can be achieved through custom serializers. For example, in the Spring framework, Jackson's ObjectMapper can be configured:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_NUMBERS_AS_STRINGS, true);
This ensures all numeric types (including Long) are converted to strings during JSON serialization. The frontend can then process them using BigInt or strings to avoid precision loss.
Solution 2: Leveraging JavaScript's BigInt Type
ES2020 introduced the BigInt type, designed to represent integers with arbitrary precision. If the backend returns numeric values as strings, the frontend can easily convert them:
let bigIntValue = BigInt(data); // data is the string "793548328091516928"
console.log(bigIntValue.toString()); // outputs the exact value
Note that BigInt cannot be directly mixed with regular numbers in arithmetic operations, and compatibility must be considered (modern browsers generally support it).
Solution 3: Third-Party Big Number Libraries
For environments without BigInt support or scenarios requiring complex mathematical operations, third-party libraries like BigNumber.js, decimal.js, or math.js can be used. These libraries provide high-precision numerical computation capabilities, for example:
let bn = new BigNumber(data); // data as string or number
console.log(bn.toFixed()); // outputs exact string representation
Solution 4: Custom Serialization and Deserialization
For complex POJO objects, serialization logic can be customized for Long-type fields. On the Java side, use the @JsonSerialize annotation:
public class MyPojo {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
// other fields
}
On the JavaScript side, a generic parsing function can be written to automatically detect and convert numeric fields that may lose precision.
Solution Comparison and Selection Recommendations
Each solution has its applicable scenarios:
- String Transmission: Simple to implement, best compatibility, but may increase data transfer size and require additional type handling on the frontend.
- BigInt: Natively supported, good performance, suitable for modern browser environments, but older browsers require polyfills.
- Third-Party Libraries: Powerful features, ideal for complex calculations, but add project dependencies and bundle size.
- Custom Serialization: High flexibility, precise control over specific fields, but higher implementation and maintenance costs.
In practical projects, selection should consider factors such as browser compatibility requirements, data precision needs, performance considerations, and team technology stack. For most applications, combining string transmission with frontend BigInt processing is a balanced approach that ensures precision and efficiency.
Conclusion
The fundamental differences in numeric representation between Java and JavaScript can lead to precision loss when transferring long integer data. By understanding the limitations of the IEEE 754 floating-point standard, developers can anticipate and mitigate such issues. The solutions proposed in this article address this challenge from various angles, with the core idea being to transmit values beyond JavaScript's safe integer range in non-numeric forms (e.g., strings) and handle them appropriately on the frontend. As web standards evolve, new features like BigInt offer better support for high-precision calculations, but backward compatibility must be carefully considered. Defining numerical precision requirements during architectural design and selecting appropriate data exchange strategies are key to building robust cross-platform applications.