JavaScript数据精度缺失问题

本文探讨了JavaScript在处理数值计算时遇到的精度缺失问题,尤其是涉及金额计算时的挑战。文章通过实例解释了为何0.1+0.2不等于0.3,并介绍了使用10的n次幂和number-precision库来解决这一问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说到数据精度缺失的问题,就想起今年夏天刚转正做的一个项目。当时的需求涉及到金额的计算,那时候对js的了解比现在还少,不过当时关于金额计算这块js函数的封装是项目里架构师写的,印象很深,当时他就直呼:被JS精度缺失坑了一把!最近看资料又接触到精度缺失,那就自己站在巨人们的肩膀上整理一下。

JS中数据类型Number

不像Java,数字型的数据类型有short,int,long,float,double等,数字类型的数据在JS中只有一种数据类型——Number。那它的底层是怎么表示的呢?我们参考菜鸟教程的一段话,它是这么说的:

JavaScript采用IEEE754标准定义的64位浮点格式表示数字,它能表示最大值为±1.7976931348623157 x
10308,最小值为±5 x 10 -324。

数据的存储格式用图形表示如下↓
图片描述

(这里先给自己留个坑,等搞清楚了具体的浮点数计算方法后再填……)

实例演示讲解

我们在控制台上做如下的十进制的加法运算,如下图所示

clipboard.png

表面上我们做的是十进制的加法,实际上计算机在底层把它换算成了二进制,再做运算。但是0.1和0.2用二进制表示的话位数是无法穷尽的。因此我们看到的0.1用二进制表示的某数只是真实的0.1的一个近似数。0.2也是这个道理。所以实际上0.1+0.2是两个近似数的相加,因此这个结果也就是0.3的近似数啦。这里不做十进制小数转二进制的详细算法,感兴趣的小伙伴可以继续深入研究。

遇到这种问题,如何解决

  • 10的n次幂运算
    把数字乘以10的n次幂,转换成计算机能够精确识别的整数,之后再除以相同的10的n次幂。

clipboard.png

  • number-precision
    这是一个挺好用的JS数据运算工具,它也是为了解决JS数据精度缺失而产生的。用法如下(官网的截图)

图片描述

<think>我们正在处理用户关于Cesium加载WFS服务时遇到的精度缺失问题。 首先,我们需要理解问题:当使用Cesium加载WFS(Web Feature Service)服务时,用户遇到了精度损失,即坐标数据在渲染时可能出现了不准确的情况。 可能的原因: 1. WFS服务返回的数据精度不足(例如,数据本身存储为低精度)。 2. Cesium在解析或渲染时对坐标进行了不适当的处理(例如,四舍五入或浮点数精度问题)。 3. 坐标参考系统(CRS)转换过程中的精度损失。 解决方案思路: 1. 确保WFS服务返回高精度数据:检查WFS服务的请求和响应,确认请求中是否指定了返回数据精度(如输出格式、小数位数等)。 2. 检查Cesium的坐标处理:Cesium使用笛卡尔空间直角坐标系(Cartesian3)来表示位置,其内部使用双精度浮点数。但是,如果数据在转换过程中(例如从经纬度到笛卡尔坐标)处理不当,可能导致精度损失。 3. 坐标参考系统:确认WFS服务使用的CRS与Cesium使用的CRS(通常是EPSG:4326)是否一致。如果不一致,需要进行转换,而转换过程可能引入误差。 具体步骤: 1. 检查WFS请求:确保请求中指定了返回的数据格式为高精度。例如,使用GML格式时,可以检查坐标的小数位数。 2. 在Cesium中,使用`Cesium.Cartographic.fromCartesian`方法将笛卡尔坐标转换为经纬度(弧度),然后转换为度,检查精度是否与原始数据一致。 3. 如果WFS服务使用的是其他CRS(如EPSG:3857),则需要在客户端进行坐标转换。在转换过程中,使用高精度的转换库(如proj4)来减少误差。 另外,Cesium在处理位置时,对于远离地心的位置可能会使用相对坐标(以相机为中心)来优化性能,但这通常不会导致精度损失,因为原始坐标仍然以高精度存储。 参考讨论: 根据Cesium社区的讨论,有类似问题报告,例如: - 在加载GeoJSON时,如果坐标是整数(如度数为整数),则显示位置不精确。解决方案是确保数据源使用足够的小数位数[^1]。 - 使用Cesium的`GeoJsonDataSource`加载数据时,可以通过设置`clampToGround`为true来将点固定到地面,但线、面可能需要使用其他方法[^2]。 但是,用户使用的是WFS服务,因此可能需要直接使用`Cesium.WebMapServiceImageryProvider`或通过第三方库(如OpenLayers)加载WFS,然后将几何图形转换为Cesium实体。 建议的解决方案: 1. 直接使用WFS服务的GeoJSON输出(如果支持)并检查精度。WFS服务通常支持多种输出格式,如GeoJSON、GML等。GeoJSON在JavaScript中更易处理。 2. 如果使用GML,需要解析GML并提取坐标,然后创建Cesium实体。在解析过程中,确保保留足够的精度。 代码示例(使用GeoJSON): 如果WFS服务支持GeoJSON输出,可以这样加载: ```javascript Cesium.GeoJsonDataSource.load('http://example.com/geoserver/wfs?request=GetFeature&...&outputFormat=application/json').then(function(dataSource) { viewer.dataSources.add(dataSource); }); ``` 在加载GeoJSON时,Cesium会将其转换为实体。如果发现精度问题,可以检查GeoJSON中的坐标值是否包含足够的小数位。 如果WFS服务不支持GeoJSON,而是返回GML,则可以使用第三方库(如OpenLayers)解析WFS响应,然后将几何图形转换为Cesium实体。在转换过程中,注意保留坐标精度。 另一种方法:使用Cesium的`Cesium.WebMapServiceImageryProvider`加载WMS(作为影像),但WFS是矢量数据,所以不适用。因此,我们主要关注矢量数据的加载。 精度问题可能出现在数据解析阶段。例如,如果使用JavaScript解析坐标字符串时,没有保留足够的小数位,就会导致精度损失。因此,在解析时,应使用`parseFloat`并确保不截断小数。 总结: 1. 确认WFS服务返回的数据精度:通过查看原始响应数据,检查坐标值的小数位数。 2. 在Cesium中,使用高精度的方式创建实体:直接使用原始坐标值(未经截断)创建Cesium的实体(如点、线、面)。 3. 如果需要进行CRS转换,使用高精度转换库。 相关问题1. 如何检查WFS服务的响应数据精度? 2. 在Cesium中如何创建高精度的实体? 3. 如何在Cesium中进行坐标参考系统转换? 下面,我们将详细解答用户的问题。</think>### Cesium加载WFS服务精度缺失问题分析及解决方案 在使用Cesium加载WFS(Web Feature Service)服务时遇到精度缺失问题,通常由以下原因导致: 1. **坐标参考系统(CRS)转换问题** WFS服务默认使用EPSG:4326(WGS84)坐标系,但某些服务可能使用其他CRS(如EPSG:3857)。转换过程中浮点数精度损失会导致偏移: $$ \Delta \text{precision} = f(\text{CRS}_\text{source}) - g(\text{CRS}_\text{target}) $$ 解决方法: - 在WFS请求中显式指定CRS: ```http &srsName=EPSG:4326 ``` - 使用`proj4js`库进行高精度坐标转换[^1] 2. **几何数据简化问题** GeoServer等WFS服务默认会简化几何图形(如`maxDecimals=4`),导致坐标截断: ```xml <!-- 原始坐标 --> <gml:pos>113.12345678 23.12345678</gml:pos> <!-- 简化后 --> <gml:pos>113.1235 23.1235</gml:pos> ``` 解决方法: - GeoServer中禁用简化:在数据存储的`高级选项`中设置`Decimal places max`为`-1` - 请求参数强制精度:`&format_options=rounding:max:15` 3. **Cesium渲染优化机制** Cesium默认对距离>100km的实体启用LOD简化,可通过以下代码禁用: ```javascript viewer.resolutionScale = 1.0; // 禁用分辨率缩放 viewer.scene.postProcessStages.fxaa.enabled = false; // 关闭抗锯齿优化 ``` 4. **GeoJSON解析精度损失** 使用`Cesium.GeoJsonDataSource`时,JSON.parse()会降低精度: ```javascript // 错误方式:直接解析字符串 const dataSource = await Cesium.GeoJsonDataSource.load(geojsonString); // 正确方式:使用自定义解析器 const geoJson = JSON.parse(geojsonString); geoJson.features.forEach(f => { f.geometry.coordinates = fixPrecision(f.geometry.coordinates, 8); }); const dataSource = await Cesium.GeoJsonDataSource.load(geoJson); ``` **完整解决方案示例:** ```javascript async function loadHighPrecisionWFS() { // 1. 获取原始GML数据 const response = await fetch('http://geoserver/wfs?request=GetFeature&srsName=EPSG:4326&outputFormat=application/json'); const geojson = await response.json(); // 2. 精度修复(保留8位小数) const precisionFix = coords => coords.map(c => Array.isArray(c) ? precisionFix(c) : parseFloat(c.toFixed(8)) ); geojson.features.forEach(f => { if (f.geometry) f.geometry.coordinates = precisionFix(f.geometry.coordinates); }); // 3. 加载到Cesium const dataSource = await Cesium.GeoJsonDataSource.load(geojson, { clampToGround: true }); viewer.dataSources.add(dataSource); // 4. 禁用优化 viewer.scene.globe.depthTestAgainstTerrain = true; viewer.scene.postProcessStages.fxaa.enabled = false; } ``` **验证方法:** 1. 比较原始WFS坐标与Cesium实体坐标: ```javascript const position = entity.position.getValue(Cesium.JulianDate.now()); const cartographic = Cesium.Cartographic.fromCartesian(position); console.log(Cesium.Math.toDegrees(cartographic.longitude), Cesium.Math.toDegrees(cartographic.latitude)); ``` 2. 使用`<ctrl> + <shift> + I`打开Cesium Inspector检查渲染精度问题仍存在,建议检查WFS服务端的GML生成配置,或使用WFS-T(事务)服务直接获取二进制几何数据[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值