关于Long转String和js丢失精度截断的问题

当Java后端使用Long类型字段并通过Web接口返回给前端时,由于JavaScript数值精度限制,可能导致数值截断。超过JS最大安全整数(2^53-1)的Long值会被截断显示为000结尾的值。解决方案包括:1)一开始就使用String类型;2)全局转换Long为String,可使用Jackson库的配置或注解方式;3)使用注解@JsonSerialize将特定字段转换为String。配置Jackson的方式有多种,但某些配置可能会影响Swagger UI的正常使用。最佳实践是采用配置全局转换,减少代码改动并避免影响其他类型序列化。

背景

Java后端的 Long(long) 类型字段,在web接口返回这种类型的值给前端时,会发生截断(即js会精度丢失,因为后端的Long的最大值超过了前端js能表示的值)

哪些情况下发生截断
  • 如果前端不是用 js框架则不会截断,比如用postman调用就不会截断,浏览器地址栏直接请求也不会截断
  • 前端用 js 类型的框架可能会发生截断,但是只有在实际值超过js的最大范围的时候才会截断
  • 截断的一般特征是 000 结尾,比如 9223372036854776000
  • 如果 Long longValue 实际值没有超过前端js的限制,也不会出现截断

如何解决

  1. 建议项目代码编写的一开始就用String类型不要用Long

  2. 如果是接手的代码,并且按1的方法改造不切实际,可以配置全局Long转String

  3. 用注解,如下(其实第2点会有问题,我在实际项目中遇到过不生效的情况。原来生效后来可能是jar包版本升级了或者其他未知的原因导致了问题)

    @JsonSerialize(using = ToStringSerializer.class)
    

    如上注解加在想要转字串的字段上,比如Long/long/Integer/int等字段上

    上述详细的包名是

    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

总结:

总体来说我觉得第2中方法应该是最好的,

如果实在没办法用3也不错,用3的话不需要改太多代码。

1这种虽然最彻底,改动也大,容易引发新的bug,虽然 Long 改 String 这种会触发编译上的报错,提示你需要改动的地方,但是也有一些bug很隐蔽

第一个是 Map#get(Object key) 方法,就算你用了泛型,比如 Map<Long, Object>改成了 Map<String, Object>,但是 map.get(key) 不管这个 key 是 Long 还是 String 都不会报错,但是实际 get 的话会get 不到值。

第二个是equals方法,equals() 的入参是 Object

全局 Long 转 String 的配置

对于

@Data
public class DemoDTO {
    private Long longValue;
    private Integer intValue;

    private long primitiveLongValue;
    private int primitiveIntValue;
}
@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
    DemoDTO result = new DemoDTO();
    result.setLongValue(Long.MAX_VALUE);
    result.setIntValue(Integer.MAX_VALUE);
    result.setPrimitiveLongValue(Long.MAX_VALUE);
    result.setPrimitiveIntValue(Integer.MAX_VALUE);
    return result;
}
1、配置一
@Configuration
public class LongToStringConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
                .serializerByType(Long.class, ToStringSerializer.instance)
                .serializerByType(Long.TYPE, ToStringSerializer.instance);
    }

实测可以将 Long/long 转字串,对于 Integer/int 等其他类型不会

2、配置二
@Configuration
public class LongToStringConfig {
    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        mapper.registerModule(simpleModule);
        converter.setObjectMapper(mapper);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return converter;
    }
}

这种写法也是可以的

3、配置三

这是个无效的方法,网上也列出了很多这种写法,列在这里是为了警示。主要是 @EnableWebMvc 注解导致 swagger 无法用,不加又导致Long转String没法用。

有解决办法,但是实在不想用这么麻烦的方法了,都有前面2种好的方法了,这里第3种的写法就作罢了

package com.example.demo.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

/**
 * 【这是个无效的方法,网上也列出了很多这种写法,列在这里是为了警示。主要是 @EnableWebMvc 注解导致 swagger 无法用,不加又导致Long转String没法用】
 * 这是第三种方法,除了 LongToStringConfig 里的二选一,这是第三种方法解决 Long 转 String
 * 说明:
 * 网上有些资料还是继承 WebMvcConfigurerAdapter 的,这个已经 @Deprecated 了,说明得比较清晰了就是因为有default 关键字后,
 * 接口的方法也可以有默认的实现,所以不需要这个 adapter 了 (PS:可以看出,很多的 adapter 的设计就是为了提供方法的空实现,
 * 以便避开需要实现接口所有方法的麻烦,使用 extends adapter 只需要重写感兴趣的方法)
 */
@Configuration
// 这个注解会导致swagger打不开(已经打开的页面重新刷新一下就无法再次打开了),参考 https://blog.csdn.net/Apoca_lypse/article/details/115759895。
// 但是不用这个注解又会导致无法将Long转String
// 这里有解决办法,但是实在不想用这么麻烦的方法了,都有前面2种好的方法了,这里第3种的写法就作罢了  (解决:https://blog.csdn.net/axiang_/article/details/107103879)
@EnableWebMvc
public class LongToStringConfig2 implements WebMvcConfigurer /*extends WebMvcConfigurerAdapter*/ {


    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);

        converter.setObjectMapper(objectMapper);
        converters.add(converter);
    }
}

实验过程

@Data
public class DemoDTO {
    private Long longValue;
    private Integer intValue;

    private long primitiveLongValue;
    private int primitiveIntValue;
}

@ApiOperation("test1 接口")
@GetMapping("/test1")
public DemoDTO test1() {
    DemoDTO result = new DemoDTO();
    result.setLongValue(Long.MAX_VALUE);
    result.setIntValue(Integer.MAX_VALUE);
    result.setPrimitiveLongValue(Long.MAX_VALUE);
    result.setPrimitiveIntValue(Integer.MAX_VALUE);
    return result;
}
  • 原生 swagger UI 发生了截断,因为它是用js解析并展示值的
    在这里插入图片描述

  • 第三方的新的 swagger UI 并不会发生截断,因为它类似postman一样不是将值用js展示
    在这里插入图片描述

  • 浏览器地址栏直接访问(刚好接口是GET,否则也不能直接访问),不截断
    在这里插入图片描述

  • postman 工具访问,不截断 (要截断就大事情了,一个工具做成这样子)
    在这里插入图片描述

  • 如果虽然这个字段定义是 Long(long) 但是如果还没超过前端 js 的限制,也不会发生截断

    @ApiOperation("test1 接口")
    @GetMapping("/test1")
    public DemoDTO test1() {
        DemoDTO result = new DemoDTO();
        result.setLongValue((long) Integer.MAX_VALUE);// 低于阈值,不会被截断  (这个值前端js还不会发生截断)
        result.setIntValue(Integer.MAX_VALUE);
        result.setPrimitiveLongValue((long) Integer.MAX_VALUE);// 低于阈值,不会被截断
        result.setPrimitiveIntValue(Integer.MAX_VALUE);
        return result;
    }
    

    在这里插入图片描述

  • 如下,需要的字段加上 @JsonSerialize(using=ToStringSerializer.class) 即可

    @Data
    public class DemoDTO {
        @JsonSerialize(using = ToStringSerializer.class)
        private Long longValue;
        private Integer intValue;
    
        @JsonSerialize(using = ToStringSerializer.class)
        private long primitiveLongValue;
        private int primitiveIntValue;
    }
    

    传统 swagger UI 上显示的结果为

    {
      "longValue": "9223372036854775807",
      "intValue": 2147483647,
      "primitiveLongValue": "9223372036854775807",
      "primitiveIntValue": 2147483647
    }
    

    不限制于 Long/long,对于 Integer/int 也可以,实际上对于任何类型的字段都是可以加这个注解的

  • 试一下其他类型加 @JsonSerialize(using=ToStringSerializer.class)

    @Data
    public class DemoDTO {
        @JsonSerialize(using = ToStringSerializer.class)
        private Long longValue;
        @JsonSerialize(using = ToStringSerializer.class)
        private Integer intValue;
    
        @JsonSerialize(using = ToStringSerializer.class)
        private long primitiveLongValue;
        @JsonSerialize(using = ToStringSerializer.class)
        private int primitiveIntValue;
    
        @JsonSerialize(using = ToStringSerializer.class)
        private List<String> list;
    
        @JsonSerialize(using = ToStringSerializer.class)
        private Date date;
    }
    
    
    @ApiOperation("test1 接口")
    @GetMapping("/test1")
    public DemoDTO test1() {
        DemoDTO result = new DemoDTO();
        result.setLongValue(Long.MAX_VALUE);
        result.setIntValue(Integer.MAX_VALUE);
        result.setPrimitiveLongValue(Long.MAX_VALUE);
        result.setPrimitiveIntValue(Integer.MAX_VALUE);
        List<String> list = new ArrayList<>();
        list.add("one");
        result.setList(list);
        result.setDate(new Date());
        return result;
    }
    

    传统 swagger UI 上显示的结果为

    {
      "longValue": "9223372036854775807",
      "intValue": "2147483647",
      "primitiveLongValue": "9223372036854775807",
      "primitiveIntValue": "2147483647",
      "list": "[one]",
      "date": "Wed Aug 11 14:46:15 CST 2021"
    }
    

    如果都没有注解,可以看到结果:(着重观察下 list 和 date)

    {
      "longValue": 9223372036854776000,
      "intValue": 2147483647,
      "primitiveLongValue": 9223372036854776000,
      "primitiveIntValue": 2147483647,
      "list": [
        "one"
      ],
      "date": "2021-08-11T06:49:10.238+00:00"
    }
    
<think>首先,用户的问题是:如何将string类型的小数换为long类型?这是一个编程相关的问题,涉及将字符串(小数形式)换成long整数类型。用户指定了编程语言,但在这个问题中没有明确提到哪种语言,但从引用的内容来看,包括C++、JavaC#,我应该覆盖常见编程语言。回顾系统级指令:-所有行内数学表达式使用$...$格式。-独立公式使用$$...$$格式。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾添加引用标识,如[^1]。此外,用户提供了几个引用,我需要参考它们。这些引用涉及C++的char()类型换、C++的stoiatoi函数、Java的字符串换到long、C#的换方法等。关键引用:-[^1]:讨论C++中的类型换,特别是char()类型,但不直接相关。-[^2]:详细比较C++的atoistoi函数,强调stoi的类型安全。-[^3]:展示了Java中将字符串换为long的示例,使用Long.parseLong()。-[^4]:讨论类型换的一般性,包括其他语言如JavaScriptC#的换方法。-[^5]:似乎乱码,但提到C++中将double换为long,以及一些类型定义。用户的具体需求是:将string类型的小数换为long类型。小数意味着数字字符串如"3.14",要换成long整数(通常是64位整数)。用户提到"小数",所以要考虑处理浮点数字符串到长整数。换涉及两种主要方式:1.直接截断小数部分:例如,"3.14"成3L。2.四舍五入或其他操作。但标准方式通常是截断小数部分,因为long是整数类型。现在,针对不同编程语言,我需要提供实现方法:-Java:引用[^3]提到使用Long.parseLong(),但这个函数要求字符串必须是整数形式。如果字符串是小数,它会抛出异常,因为Long.parseLong()只能处理整数字符串。用户是小数,所以不能直接使用。需要先解析为double或float,再换为long。-C++:引用[^2]讨论了stoi,但stoi也是用于整数字符串。对于小数,需要使用stod或stold来解析为double,然后换为long。-C#:引用[^4]提到C#的int.Parse()或double.Parse(),类似地,需要先解析为double,再换为long。用户说"编程语言",所以我应该覆盖常见语言:Java,C++,C#,Python等,从引用看,引用了JavaC++。结构回答:-先解释挑战:字符串小数换为long整数。-然后提供各个语言的实现。使用中文回答。最后,生成相关问题。添加引用标识:由于用户提供了引用,我应该参考它们。生成相关问题:结束时,类似于示例。回答结构:1.概述问题。2.各个语言的具体方法。-Java-C++-C#-Python(既然用户提到,我可以添加)3.注意事项:如溢出、异常处理。4.示例代码。现在,针对用户问题:"我想将string类型的小数换为long类型的方法或实现"所以,步骤:-输入:一个字符串,如"3.14"-输出:一个long值,如3L核心方法是:-先使用语言提供的函数将字符串解析为浮点数(如double)。-然后将浮点数换为long整数(通常通过类型换或函数)。在每种语言中:-Java:使用Double.parseDouble()换为double,然后(long)强制换。-C++:使用std::stod()换为double,然后static_cast<long>()或直接long类型换。-C#:使用double.Parse(),然后(long)或Convert.ToInt64()。-Python:使用float()换为float,然后int()换为int(Python的int是任意精度,但用户指定long,类似)。在JavaC#中,long是特定类型;在Python,int处理大数。参考引用:-[^3]在Java中使用Long.parseLong(),但它要求整数字符串,所以不能直接用于小数。因此,在回答中要提到这个方法不可用,需要先换。类似地,对于C++。现在,代码示例:我应该提供。使用格式:行内数学表达式如果有,使用$...$,比如数值或算法。但这个问题中可能不涉及复杂数学。独立公式可能需要,如果有数学换公式。例如,换公式是直接截断:$$\text{longValue}=\text{floor}(\text{parseDouble}(str))$$但通常是隐含的。我会在必要时添加。结束语:生成相关问题。相关问题例如:-如何处理换时的溢出?-如何在其他语言中实现?-如何添加四舍五入?确保回答真实可靠。现在,草拟回答。</think> ### 将string类型的小数换为long类型的实现方法 将字符串string)类型的小数(如"3.14")换为long整数类型是编程中常见的需求。long类型通常表示64位整数,主要用于存储大范围整数值(正负数)。小数字符串换为long的关键在于先将字符串解析为浮点数(如double),然后对浮点数进行截断操作(去除小数部分)换为long整数。如果小数部分是负数,截断同样有效(例如,"-3.14" 换为 -3L)。然而,需要注意类型安全:输入字符串必须是有效的数字格式,否则会引发异常(如NumberFormatException或invalid_argument);同时,浮点数可能超出long的数值范围(例如,±9,223,372,036,854,775,807为long的最大/最小值),需要处理潜在的溢出问题。 以下,我将针对常见编程语言(如Java、C++、C#Python)提供详细实现方法,包括代码示例关键注意事项。这些实现参考了用户提供的引用内容[^2]、[^3]、[^4],主要强调类型安全错误处理(如使用try-catch机制)。 #### 1. **Java实现** 在Java中,`Long.parseLong()`函数只能处理整数字符串(如"123"),不支持小数字符串(否则会抛出NumberFormatException)。因此,需要先用`Double.parseDouble()`将字符串解析为double类型,然后强制换为long截断小数部分)。引用[^3]展示了`Long.parseLong()`的整数换,但对小数换需调整。 **示例代码:** ```java public class StringToLongDemo { public static void main(String[] args) { String str = "3.14"; // 输入小数字符串 try { double doubleValue = Double.parseDouble(str); // 第一步:解析为double long longValue = (long) doubleValue; // 第二步:截断小数部分换为long System.out.println("换结果: " + longValue); // 输出: 换结果: 3 } catch (NumberFormatException e) { System.out.println("字符串格式无效: " + e.getMessage()); } catch (ArithmeticException e) { // 添加异常处理以避免溢出 System.out.println("值超出long范围: " + e.getMessage()); } } } ``` - **步骤详解:** - 使用`Double.parseDouble(str)`解析字符串为double类型(会检查字符串是否为有效小数)。 - 通过强制换`(long) doubleValue`截断小数部分(不四舍五入),例如"3.14" → 3L,"-3.14" → -3L。 - 错误处理:捕获`NumberFormatException`以处理非法字符串(如"abc");为防止double值过大溢出long范围,捕获`ArithmeticException`。 - **注意事项:** - 如果字符串是整数(如"123.0"),换结果为123L。 - 对于大值(如 "1.23456789012345e18"),double可能丢失精度,需先检查范围再换[^4]。 #### 2. **C++实现** 在C++中,`std::stoi()`或`std::stol()`等函数只能处理整数字符串,引用[^2]强调了`stoi`的类型安全性(抛出异常处理范围问题)。对于小数字符串,需要使用`std::stod()`解析为double类型,然后用`static_cast<long>`换。建议优先使用`std::stod()`(支持小数)而非`std::atoi`(不安全,不检查范围)。 **示例代码:** ```cpp #include <iostream> #include <string> #include <stdexcept> // 包含标准异常库 int main() { std::string str = "3.14"; // 输入小数字符串 try { double doubleValue = std::stod(str); // 第一步:解析为double // 第二步:检查并换为long截断小数) if (doubleValue > static_cast<double>(std::numeric_limits<long>::max()) || doubleValue < static_cast<double>(std::numeric_limits<long>::min())) { throw std::out_of_range("值超出long范围"); } long longValue = static_cast<long>(doubleValue); // 安全换 std::cout << "换结果: " << longValue << std::endl; // 输出: 换结果: 3 } catch (const std::invalid_argument& e) { std::cerr << "无效数字格式: " << e.what() << std::endl; } catch (const std::out_of_range& e) { std::cerr << "范围错误: " << e.what() << std::endl; } return 0; } ``` - **步骤详解:** - `std::stod(str)`将字符串解析为double(处理小数或指数形式,如"1.23e-4")。 - 手动检查double值是否在long范围内(使用`std::numeric_limits<long>::max()``::min()`获取边界),避免溢出。 - `static_cast<long>`执行截断换。 - **注意事项:** - 对无效格式(如非数字字符串)会抛出`std::invalid_argument`。 - 与引用[^2]一致,C++11引入的`stod`更安全,推荐使用[^2]。 #### 3. **C#实现** 在C#中,`long.Parse()`或`Convert.ToInt64()`只能处理整数字符串,引用[^4]提到`double.Parse()`或`Convert.ToDouble()`更适合解析小数。然后使用`Convert.ToInt64()`或强制换来截断long。C#提供`TryParse`方法避免异常,提升健壮性。 **示例代码:** ```csharp using System; class Program { static void Main() { string str = "3.14"; // 输入小数字符串 try { double doubleValue; if (double.TryParse(str, out doubleValue)) { // 第一步:安全解析为double long longValue = Convert.ToInt64(doubleValue); // 第二步:换为long截断小数) Console.WriteLine($"换结果: {longValue}"); // 输出: 换结果: 3 } else { Console.WriteLine("字符串格式无效"); } } catch (OverflowException e) { // 处理溢出 Console.WriteLine($"值超出long范围: {e.Message}"); } } } ``` - **步骤详解:** - 使用`double.TryParse(str, out doubleValue)`安全解析字符串(避免异常,返回bool指示成功与否)。 - `Convert.ToInt64(doubleValue)`自动截断小数部分换为long。 - 错误处理:捕获`OverflowException`应对大值溢出。 - **注意事项:** - 类似引用[^4],C#支持隐式强制换,但`Convert.ToInt64`处理范围更安全。 #### 4. **Python实现** Python中,字符串换简单直接:先用`float()`函数解析为浮点数,然后用`int()`换为整数(自动截断小数部分)。Python的`int`类型等同于long(支持任意大整数),因此无范围溢出问题(除非内存不足)。 **示例代码:** ```python def string_to_long_decimal(s): try: float_value = float(s) # 第一步:解析为浮点数 long_value = int(float_value) # 第二步:截断小数换为int/long print(f"换结果: {long_value}") # 输出: 换结果: 3(针对"3.14") except ValueError: print("字符串格式无效") except OverflowError: # 处理极端值(虽罕见) print("值超出系统支持范围") # 测试 string_to_long_decimal("3.14") ``` - **步骤详解:** - `float(s)`解析小数字符串为float类型。 - `int(float_value)`截断小数部分,获得整数。 - **注意事项:** - Python的动态类型简化了换,无需显式long类型定义。 ### 总结与最佳实践 - **通用方法:** 所有语言中,先将字符串解析为浮点数(如double),再截断换为long。关键步骤: 1. 解析字符串为浮点数(检查有效性)。 2. 浮点数截断long整数(避免小数部分)。 3. 添加异常处理(如无效格式、溢出)。 - **注意事项:** - **小数处理:** 截断是非四舍五入操作;如果需要四舍五入,需额外实现(如Java中用`Math.round()`再换)。 - **类型安全:** 优先使用现代函数(如Java的`parseDouble`、C++的`stod`),它们提供异常处理(优于不安全函数如C++的`atoi`)[^2][^4]。 - **性能考虑:** 在性能敏感场景中,避免多重换;对于大数组,先进行范围检查。 - **语言无关建议:** 测试边界值(如"", "abc", "9999999999999999999.5")以确保健壮性。 对于小数换,数学表达式可表示为: $$\text{longValue} = \lfloor \text{parseFloat}(str) \rfloor$$ 其中$\lfloor \cdot \rfloor$表示向下取整(即截断)[^4]。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值