sni*_*n21 5 json rust serde serde-json reqwest
对于 Rust 来说相对较新。我正在尝试进行 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;
Run Code Online (Sandbox Code Playgroud)
我正在序列化的结构
#[derive(Serialize, Deserialize, Clone, Debug)]
struct OrderCreationEvent {
order_amount: f32,
customer_details: ...,
order_meta: ...,
}
Run Code Online (Sandbox Code Playgroud)
例如。这里的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?;
Run Code Online (Sandbox Code Playgroud)
在 json!() 之后,金额被序列化为 15.379345234542。我需要15.38
我读了一些关于为 f32 编写自定义序列化器的文章,它可以截断到小数点后两位,但我对 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)
}
Run Code Online (Sandbox Code Playgroud)
无论自定义序列化器是否是问题的正确方法或解决方案,我仍然想学习如何编写一个序列化器,所以也请随时启发我。干杯! :)
TL;DRserde_json这是一个从扩大f32到的浮点问题f64。您可以使用像 一样简单的代码来重现它println!("{}", 77.63_f32 as f64)。要修复它,您需要转换为f64,然后舍入,并将其序列化为f64:
s.serialize_f64((*n as f64 * 100.0).trunc() / 100.0)
Run Code Online (Sandbox Code Playgroud)
代码中的问题出在与您想象的不同的地方 - 它与浮点精度有关,而不是与 serde 有关。当你写下类似这样的东西时:
let lat = 77.63_f32;
Run Code Online (Sandbox Code Playgroud)
...您指示编译器将分数 7763/100 转换为f32. 但该数字不能用 an 精确表示,f32因为f32(像所有二进制浮点类型一样)使用二进制分数,即分母是某些大小限制内的 2 的幂的有理数。考虑到这些限制,7763/100 近似为 10175119/2**17。1如果您尝试打印该f32值,您将得到预期的 77.63输出,因为println!()知道它正在打印f32第 7 个之后的所有数字都是近似值的副作用并被丢弃。
serde_json工作方式不同 - 它f32通过将值转换为f64序列化值,因为这是 JSON 和 JavaScript 使用的精度。不幸的结果是,10175119/2**17 的近似值77.63_f32被扩大到f64没有存储 77.63 的原始愿望的上下文。简单f64地存储近似值(它可以精确地容纳它,而不会进一步损失精度),当您打印结果 时f64,您会得到77.62999725341797,这就是 10175119/2**17 十进制到 16 位精度的样子。
这就是为什么实现自定义序列化s.serialize_f32(f32::trunc(*x * 100.0) / 100.0)没有任何效果 - 您将 af32四舍五入到两位小数(这在您的程序中是无操作的,因为它一开始就四舍五入),然后将其传递给serialize_f32(). serialize_f32()继续扩大该f32值,f64使近似值中的额外数字可见f32- 并且您回到了使用 serde 生成的实现开始的地方。
正确的版本必须转换f32为f64,然后去掉f64 类型中多余的数字,然后将其传递给serialize_f64()打印:
s.serialize_f64((*n as f64 * 100.0).trunc() / 100.0)
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为:数字77.63_f32被转换为f64对应于 10175119/2**17 (即不是 77.63_f64,这将近似为2到 682840701314007/2**43)。然后,该数字在 f64 中四舍五入为两位数,并且该四舍五入产生最接近的 77.63 的近似值f64。即现在我们得到了与 Rust 源代码中使用的相同的 682840701314007/2**43 近似值77.63_f64。这是 serde 将使用的数字,并将serde_json其格式化为77.63JSON 输出。
旁注:上面的代码使用trunc()以下问题中的尝试,但也许round() 如此处所示将是更合适的选择。
1 您可以使用此 Python 单行代码获得此比率:
s.serialize_f64((*n as f64 * 100.0).trunc() / 100.0)
Run Code Online (Sandbox Code Playgroud)
2 同样使用Python获得:
let lat = 77.63_f32;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1372 次 |
| 最近记录: |