Skip to content

Commit e4682df

Browse files
fix: Encode negative zero float and double fields. Fixes #1177
1 parent aed74ad commit e4682df

3 files changed

Lines changed: 139 additions & 8 deletions

File tree

prost-derive/src/field/scalar.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ impl Field {
105105
}
106106
}
107107

108+
/// Returns an expression which evaluates to `true` if the field differs from its default
109+
/// value, and therefore has to be encoded.
110+
///
111+
/// Floating point types are compared by bit pattern, so that `-0.0` is not considered equal
112+
/// to `0.0`. All other types are compared with `PartialEq`.
113+
fn is_not_default(&self, ident: &TokenStream, default: &DefaultValue) -> TokenStream {
114+
let default = default.typed();
115+
match self.ty {
116+
Ty::Float | Ty::Double => quote!(#ident.to_bits() != (#default).to_bits()),
117+
_ => quote!(#ident != #default),
118+
}
119+
}
120+
108121
pub fn encode(&self, prost_path: &Path, ident: TokenStream) -> TokenStream {
109122
let module = self.ty.module();
110123
let encode_fn = match self.kind {
@@ -117,9 +130,9 @@ impl Field {
117130

118131
match self.kind {
119132
Kind::Plain(ref default) => {
120-
let default = default.typed();
133+
let is_not_default = self.is_not_default(&ident, default);
121134
quote! {
122-
if #ident != #default {
135+
if #is_not_default {
123136
#encode_fn(#tag, &#ident, buf);
124137
}
125138
}
@@ -171,9 +184,9 @@ impl Field {
171184

172185
match self.kind {
173186
Kind::Plain(ref default) => {
174-
let default = default.typed();
187+
let is_not_default = self.is_not_default(&ident, default);
175188
quote! {
176-
if #ident != #default {
189+
if #is_not_default {
177190
#encoded_len_fn(#tag, &#ident)
178191
} else {
179192
0

prost/src/types.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ impl Name for i64 {
232232
/// `google.protobuf.FloatValue`
233233
impl Message for f32 {
234234
fn encode_raw(&self, buf: &mut impl BufMut) {
235-
if *self != 0.0 {
235+
// Compare bit patterns so that `-0.0` is not considered equal to `0.0`.
236+
if self.to_bits() != 0f32.to_bits() {
236237
float::encode(1, self, buf)
237238
}
238239
}
@@ -250,7 +251,7 @@ impl Message for f32 {
250251
}
251252
}
252253
fn encoded_len(&self) -> usize {
253-
if *self != 0.0 {
254+
if self.to_bits() != 0f32.to_bits() {
254255
float::encoded_len(1, self)
255256
} else {
256257
0
@@ -274,7 +275,8 @@ impl Name for f32 {
274275
/// `google.protobuf.DoubleValue`
275276
impl Message for f64 {
276277
fn encode_raw(&self, buf: &mut impl BufMut) {
277-
if *self != 0.0 {
278+
// Compare bit patterns so that `-0.0` is not considered equal to `0.0`.
279+
if self.to_bits() != 0f64.to_bits() {
278280
double::encode(1, self, buf)
279281
}
280282
}
@@ -292,7 +294,7 @@ impl Message for f64 {
292294
}
293295
}
294296
fn encoded_len(&self) -> usize {
295-
if *self != 0.0 {
297+
if self.to_bits() != 0f64.to_bits() {
296298
double::encoded_len(1, self)
297299
} else {
298300
0

tests/src/message_encoding.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,122 @@ fn check_scalar_types() {
3737
check_message(&ScalarTypes::default());
3838
}
3939

40+
/// A message with implicit presence floating point fields.
41+
#[derive(Clone, PartialEq, Message)]
42+
pub struct Zeroes {
43+
#[prost(float, tag = "1")]
44+
pub float: f32,
45+
#[prost(double, tag = "2")]
46+
pub double: f64,
47+
}
48+
49+
/// `-0.0` differs from the default `0.0` and therefore has to be encoded, otherwise the sign bit
50+
/// is silently lost. See <https://github.com/tokio-rs/prost/issues/1177>.
51+
#[test]
52+
fn check_negative_zero_is_encoded() {
53+
const FLOAT: &[u8] = &[0x0d, 0x00, 0x00, 0x00, 0x80];
54+
const DOUBLE: &[u8] = &[0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80];
55+
let both = [FLOAT, DOUBLE].concat();
56+
57+
let msg = Zeroes {
58+
float: -0.0,
59+
double: -0.0,
60+
};
61+
assert_eq!(msg.encoded_len(), both.len());
62+
assert_eq!(msg.encode_to_vec(), both);
63+
64+
// Decoding preserves the sign, and re-encoding reproduces the original bytes.
65+
let decoded = Zeroes::decode(both.as_slice()).unwrap();
66+
assert_eq!(decoded.float.to_bits(), (-0.0f32).to_bits());
67+
assert_eq!(decoded.double.to_bits(), (-0.0f64).to_bits());
68+
assert_eq!(decoded.encoded_len(), both.len());
69+
assert_eq!(decoded.encode_to_vec(), both);
70+
71+
// An absent field still decodes to a positive zero which is not re-encoded.
72+
let decoded = Zeroes::decode(FLOAT).unwrap();
73+
assert_eq!(decoded.float.to_bits(), (-0.0f32).to_bits());
74+
assert_eq!(decoded.double.to_bits(), (0.0f64).to_bits());
75+
assert_eq!(decoded.encoded_len(), FLOAT.len());
76+
assert_eq!(decoded.encode_to_vec(), FLOAT);
77+
}
78+
79+
/// `0.0` is the default value and must not be encoded.
80+
#[test]
81+
fn check_positive_zero_is_not_encoded() {
82+
let msg = Zeroes {
83+
float: 0.0,
84+
double: 0.0,
85+
};
86+
assert_eq!(msg.encoded_len(), 0);
87+
assert_eq!(msg.encode_to_vec(), []);
88+
}
89+
90+
/// `google.protobuf.FloatValue` and `google.protobuf.DoubleValue` have hand written `Message`
91+
/// implementations which need the same treatment.
92+
#[test]
93+
fn check_negative_zero_wrapper_types() {
94+
let float = [0x0d, 0x00, 0x00, 0x00, 0x80];
95+
assert_eq!((-0.0f32).encoded_len(), float.len());
96+
assert_eq!((-0.0f32).encode_to_vec(), float);
97+
assert_eq!(
98+
f32::decode(float.as_slice()).unwrap().to_bits(),
99+
(-0.0f32).to_bits()
100+
);
101+
assert_eq!((0.0f32).encoded_len(), 0);
102+
assert_eq!((0.0f32).encode_to_vec(), []);
103+
104+
let double = [0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80];
105+
assert_eq!((-0.0f64).encoded_len(), double.len());
106+
assert_eq!((-0.0f64).encode_to_vec(), double);
107+
assert_eq!(
108+
f64::decode(double.as_slice()).unwrap().to_bits(),
109+
(-0.0f64).to_bits()
110+
);
111+
assert_eq!((0.0f64).encoded_len(), 0);
112+
assert_eq!((0.0f64).encode_to_vec(), []);
113+
}
114+
115+
/// A message with implicit presence floating point fields with a custom default.
116+
#[derive(Clone, PartialEq, Message)]
117+
pub struct FloatDefaults {
118+
#[prost(float, tag = "1", default = "-1.5")]
119+
pub negative: f32,
120+
#[prost(double, tag = "2", default = "nan")]
121+
pub nan: f64,
122+
}
123+
124+
/// Custom floating point defaults are compared by bit pattern too. Note that this makes a field
125+
/// which is equal to a `NaN` default no longer encoded, matching every other default value.
126+
#[test]
127+
fn check_float_defaults() {
128+
let default = FloatDefaults::default();
129+
assert_eq!(default.negative.to_bits(), (-1.5f32).to_bits());
130+
assert!(default.nan.is_nan());
131+
assert_eq!(default.encoded_len(), 0);
132+
assert_eq!(default.encode_to_vec(), []);
133+
134+
// Both zeroes differ from the negative default and are encoded.
135+
for (value, expected) in [
136+
(0.0f32, [0x0d, 0x00, 0x00, 0x00, 0x00]),
137+
(-0.0f32, [0x0d, 0x00, 0x00, 0x00, 0x80]),
138+
] {
139+
let msg = FloatDefaults {
140+
negative: value,
141+
..Default::default()
142+
};
143+
assert_eq!(msg.encoded_len(), expected.len());
144+
assert_eq!(msg.encode_to_vec(), expected);
145+
}
146+
147+
let msg = FloatDefaults {
148+
nan: 0.0,
149+
..Default::default()
150+
};
151+
let expected = [0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
152+
assert_eq!(msg.encoded_len(), expected.len());
153+
assert_eq!(msg.encode_to_vec(), expected);
154+
}
155+
40156
/// A protobuf message which contains all scalar types.
41157
#[derive(Clone, PartialEq, Message)]
42158
pub struct ScalarTypes {

0 commit comments

Comments
 (0)