rust: Make .display_plain() for f64 support alternate decimal points.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 24 Aug 2025 22:17:47 +0000 (15:17 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 24 Aug 2025 22:18:25 +0000 (15:18 -0700)
rust/pspp/src/format/display/mod.rs
rust/pspp/src/sys/raw/records.rs
rust/pspp/src/sys/write.rs

index 6f65cee10e371a0678bb62fc625d2e7872e85a16..79a4c106f1decc61f9ae273cee5f1bdc2a670de1 100644 (file)
@@ -56,29 +56,63 @@ pub struct DisplayDatum<'b, B> {
 mod test;
 
 pub trait DisplayPlain {
-    fn display_plain(&self) -> impl Display;
+    fn display_plain(&self) -> DisplayPlainF64;
 }
 
 impl DisplayPlain for f64 {
-    fn display_plain(&self) -> impl Display {
-        DisplayPlainF64(*self)
+    fn display_plain(&self) -> DisplayPlainF64 {
+        DisplayPlainF64 {
+            value: *self,
+            decimal: '.',
+        }
     }
 }
 
-pub struct DisplayPlainF64(pub f64);
+pub struct DisplayPlainF64 {
+    pub value: f64,
+    pub decimal: char,
+}
+
+impl DisplayPlainF64 {
+    pub fn with_decimal(self, decimal: char) -> Self {
+        Self { decimal, ..self }
+    }
+}
 
 impl Display for DisplayPlainF64 {
     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
-        if self.0.abs() < 0.0005 || self.0.abs() > 1e15 {
-            // Print self.0s that would otherwise have lots of leading or
-            // trailing zeros in scientific notation with full precision.
-            write!(f, "{:.e}", self.0)
-        } else if self.0 == self.0.trunc() {
-            // Print integers without decimal places.
-            write!(f, "{:.0}", self.0)
-        } else {
-            // Print other numbers with full precision.
-            write!(f, "{:.}", self.0)
+        struct Inner(f64);
+
+        impl Display for Inner {
+            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+                let value = self.0;
+                if (value.abs() < 0.0005 && value != 0.0) || value.abs() > 1e15 {
+                    // Print 0s that would otherwise have lots of leading or
+                    // trailing zeros in scientific notation with full precision.
+                    write!(f, "{value:.e}")
+                } else if value == value.trunc() {
+                    // Print integers without decimal places.
+                    write!(f, "{value:.0}")
+                } else {
+                    // Print other numbers with full precision.
+                    write!(f, "{value:.}")
+                }
+            }
+        }
+
+        match self.decimal {
+            '.' => write!(f, "{}", Inner(self.value)),
+            _ => {
+                let mut tmp = SmallString::<[u8; 64]>::new();
+                write!(&mut tmp, "{}", Inner(self.value)).unwrap();
+                if let Some(position) = tmp.find('.') {
+                    f.write_str(&tmp[..position])?;
+                    f.write_char(self.decimal)?;
+                    f.write_str(&tmp[position + 1..])
+                } else {
+                    f.write_str(&tmp)
+                }
+            }
         }
     }
 }
index 5269d26d980684a4d9f938b55b93e97d25de303e..b54f8d6da3e75d6a2850d153aa9f9226f4fac8bb 100644 (file)
@@ -15,7 +15,7 @@ use crate::{
     data::{ByteStrArray, ByteString, Datum},
     dictionary::CategoryLabels,
     endian::FromBytes,
-    format::{DisplayPlainF64, Format, Type},
+    format::{DisplayPlain, Format, Type},
     identifier::{Error as IdError, Identifier},
     sys::{
         raw::{
@@ -2672,8 +2672,8 @@ pub enum ZTrailerError {
     /// ZLIB trailer bias {actual} is not {} as expected from file header bias.
     #[
         error(
-        "Bias {actual} is not {} as expected from file header.",
-        DisplayPlainF64(*expected)
+            "Bias {actual} is not {} as expected from file header.",
+            expected.display_plain()
     )]
     WrongZlibTrailerBias {
         /// ZLIB trailer bias read from file.
index 8b12ea268e987c5fbec851c942e3df8bd0346ef0..d67b73575bc4d452f8e1e627e25db5d87a36e6c1 100644 (file)
@@ -35,7 +35,7 @@ use smallvec::SmallVec;
 use crate::{
     data::{Datum, RawString},
     dictionary::{CategoryLabels, Dictionary, MultipleResponseType},
-    format::{DisplayPlainF64, Format},
+    format::{DisplayPlain, Format},
     identifier::Identifier,
     output::spv::Zeros,
     sys::{
@@ -536,7 +536,7 @@ where
 
                     let mut value = match datum {
                         Datum::Number(Some(number)) => {
-                            DisplayPlainF64(*number).to_string().into_bytes()
+                            number.display_plain().to_string().into_bytes()
                         }
                         Datum::Number(None) => vec![b'.'],
                         Datum::String(raw_string) => raw_string.0.clone(),