我正在尝试进行一个API调用,它需要序列化JSON主体。
JSON主体包含一个order_amount键,其值只能采用INR格式为100.36的值,即卢比100和派塞36。更多示例为10.48、3.20、1.09。
我面临的问题是,在serde_json中使用json!()进行序列化之后,浮点值变成了类似于100.359765464332的值。
API随后失败,因为它期望order_amount只有两个小数位。
下面是我的代码:
进口
use lambda_runtime::{handler_fn, Context, Error};
use reqwest::header::ACCEPT;
use reqwest::{Response, StatusCode};
use serde_json::json;
use std::env;
#[macro_use]
extern crate serde_derive;
我正在序列化的结构体
#[derive(Serialize, Deserialize, Clone, Debug)]
struct OrderCreationEvent {
order_amount: f32,
customer_details: ...,
order_meta: ...,
}
例如,此处order_amount的值为15.38
async fn so_my_function(
e: OrderCreationEvent,
_c: Context,
) -> std::result::Result<CustomOutput, Error> {
let resp: Response = client
.post(url)
.json::<serde_json::Value>(&json!(e))
.send()
.await?;
在json!()之后,金额被序列化为15.379345234542。我需要15.38
我读过一些关于为f32编写一个自定义序列化程序的文章,它可以截断到2个小数,但我的熟练程度在Rust方面是有限的。
所以,我发现了这个代码,并一直在修补它没有运气:
fn order_amount_serializer<S>(x: &f32, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_f32(*x)
// Ok(f32::trunc(x * 100.0) / 100.0)
}
无论自定义序列化器是否是解决问题的正确方法或解决方案,我仍然想学习如何编写一个,所以请随时在那里启发我。干杯!:)
2条答案
按热度按时间xwmevbvl1#
TL;DR这是
serde_json
扩展f32
到f64
时产生的浮点问题。您可以使用println!("{}", 77.63_f32 as f64)
这样简单的代码重现此问题。要修复此问题,您需要转换为f64
,然后取整,并将其序列化为f64
:详细说明
代码中的问题与你所想的不同--它与浮点精度有关,而与serde无关。
...您指示编译器将分数7763/100转换为
f32
。但该数字不能用f32
精确表示,因为f32
(像所有二进制浮点类型一样)使用二进制分数,即分母在一定大小限制内为2的幂的有理数。7763/100近似为10175119/217.1如果你试图打印f32
的值,你将输出get the expected 77.63,因为println!()
知道它打印的是f32
,其中第7位之后的所有数字都是近似的副作用,将被丢弃。serde_json
的工作方式不同-它通过将f32
值转换为f64
来序列化它们,因为这是JSON和JavaScript使用的精度。不幸的结果是77.63_f32
的近似值10175119/217被扩展为f64
,而没有最初存储77.63的上下文。f64
只存储近似值(它可以精确地容纳近似值,而不会进一步损失精度),当你打印得到的f64
时,你会得到get 77.62999725341797,这就是10175119/2**17在十进制到16位精度时的样子。这就是将自定义序列化实现为
s.serialize_f32(f32::trunc(*x * 100.0) / 100.0)
没有效果的原因-您将f32
舍入为两位十进制数(在您的程序中是空操作,因为它一开始是舍入的),然后将其传递给serialize_f32()
。serialize_f32()
继续将f32
的值扩展为f64
,这使得f32
近似值中的额外数字可见-您将返回到使用serde生成的实现开始的位置。正确的版本必须将
f32
转换为f64
,然后去掉f64类型 * 中的多余数字 *,然后将其传递给serialize_f64()
进行打印:Playground
这是因为:将数字
77.63_f32
转换为对应于10175119/217的f64
(即,不是77.63_f64
,它将近似为2到682840701314007/243)。然后,此数字在f64中舍入为两位数,并且舍入产生了f64
所能达到的最接近的77.63的近似值。也就是说,现在我们得到的近似值与在Rust源代码中使用77.63_f64
得到的近似值相同。s是serde将处理的数字,serde_json
将在JSON输出中将其格式化为77.63
。边注:上面的代码在问题中的尝试之后使用
trunc()
,但是round()
as shown here可能是更合适的选择。1你可以用下面的Python一行程序得到这个比率:
2也可使用Python获得:
l2osamch2#
如果你需要精确控制JSON消息的格式,
serde_json
提供了你所需要的工具。生成JSON需要通过serde_json::ser::Formatter
特性。你可以用某种方式编写write_f32
/write_f64
函数,它们只在点后面生成两位数。你可以使用现有的实现之一,并根据需要对其进行修改。