Skip to content

Commit

Permalink
worksheet: add/fix handling of NaN/Inf numbers
Browse files Browse the repository at this point in the history
Feature request #130
  • Loading branch information
jmcnamara committed Jan 29, 2025
1 parent 1899918 commit a6fb41f
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 7 deletions.
34 changes: 34 additions & 0 deletions examples/doc_worksheet_set_nan_string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

//! The following example demonstrates handling NaN and Infinity values and also
//! setting custom string representations.
use rust_xlsxwriter::{Workbook, XlsxError};

fn main() -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

// Write NaN and Infinity default values.
worksheet.write(0, 0, "Default:")?;
worksheet.write(0, 1, f64::NAN)?;
worksheet.write(1, 1, f64::INFINITY)?;
worksheet.write(2, 1, f64::NEG_INFINITY)?;

// Overwrite the default values.
worksheet.set_nan_string("Nan");
worksheet.set_infinity_string("Infinity");
worksheet.set_neg_infinity_string("NegInfinity");

// Write NaN and Infinity custom values.
worksheet.write(4, 0, "Custom:")?;
worksheet.write(4, 1, f64::NAN)?;
worksheet.write(5, 1, f64::INFINITY)?;
worksheet.write(6, 1, f64::NEG_INFINITY)?;

workbook.save("worksheet.xlsx")?;

Ok(())
}
127 changes: 123 additions & 4 deletions src/worksheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,9 @@ pub struct Worksheet {
user_default_row_height: f64,
hide_unused_rows: bool,
has_sheet_data: bool,
nan: String,
infinity: String,
neg_infinity: String,

#[cfg(feature = "constant_memory")]
pub(crate) file_writer: BufWriter<File>,
Expand Down Expand Up @@ -1724,6 +1727,9 @@ impl Worksheet {
vml_shape_id: 0,
user_default_row_height: DEFAULT_ROW_HEIGHT,
hide_unused_rows: false,
nan: "NAN".to_string(),
infinity: "INF".to_string(),
neg_infinity: "-INF".to_string(),

// These collections need to be reset on resave.
comment_relationships: vec![],
Expand Down Expand Up @@ -2563,7 +2569,7 @@ impl Worksheet {
/// of +/- 999,999,999,999,999 (15 digits).
///
/// Excel doesn't have handling for NaN or INF floating point numbers.
/// These will be stored as the strings "Nan", "INF", and "-INF" strings
/// These will be stored as the strings "NAN", "INF", and "-INF" strings
/// instead.
///
/// # Parameters
Expand Down Expand Up @@ -2652,7 +2658,7 @@ impl Worksheet {
/// of +/- 999,999,999,999,999 (15 digits).
///
/// Excel doesn't have handling for NaN or INF floating point numbers. These
/// will be stored as the strings "Nan", "INF", and "-INF" strings instead.
/// will be stored as the strings "NAN", "INF", and "-INF" strings instead.
///
/// # Parameters
///
Expand Down Expand Up @@ -13207,6 +13213,115 @@ impl Worksheet {
Ok(self)
}

/// Set the default string used for NaN values.
///
/// Excel doesn't support storing `NaN` (Not a Number) values. If a `NAN` is
/// generated as the result of a calculation Excel stores and displays
/// the error `#NUM!`. However, this error isn't usually used
/// outside of a formula result and it isn't stored as a number.
///
/// In order to deal with [`f64::NAN`] in a reasonable way `rust_xlsxwriter`
/// writes it as the string "NAN". The `set_nan_string()` method allows you
/// to override this default value.
///
/// # Parameters
///
/// - `nan`: The string to use for NaN values.
///
///
/// # Examples
///
/// The following example demonstrates handling NaN and Infinity values and also
/// setting custom string representations.
///
/// ```
/// # // This code is available in examples/doc_worksheet_set_nan_string.rs
/// #
/// # use rust_xlsxwriter::{Workbook, XlsxError};
/// #
/// # fn main() -> Result<(), XlsxError> {
/// # let mut workbook = Workbook::new();
/// # let worksheet = workbook.add_worksheet();
/// #
/// // Write NaN and Infinity default values.
/// worksheet.write(0, 0, "Default:")?;
/// worksheet.write(0, 1, f64::NAN)?;
/// worksheet.write(1, 1, f64::INFINITY)?;
/// worksheet.write(2, 1, f64::NEG_INFINITY)?;
///
/// // Overwrite the default values.
/// worksheet.set_nan_string("Nan");
/// worksheet.set_infinity_string("Infinity");
/// worksheet.set_neg_infinity_string("NegInfinity");
///
/// // Write NaN and Infinity custom values.
/// worksheet.write(4, 0, "Custom:")?;
/// worksheet.write(4, 1, f64::NAN)?;
/// worksheet.write(5, 1, f64::INFINITY)?;
/// worksheet.write(6, 1, f64::NEG_INFINITY)?;
/// #
/// # workbook.save("worksheet.xlsx")?;
/// #
/// # Ok(())
/// # }
/// ```
///
/// Output file:
///
/// <img src="https://rustxlsxwriter.github.io/images/worksheet_set_nan_string.png">
///
pub fn set_nan_string(&mut self, nan: impl Into<String>) -> &mut Worksheet {
self.nan = nan.into();
self
}

/// Set the default string used for Infinite values.
///
/// Excel doesn't support storing `Inf` (infinity) values. If an `Inf` is
/// generated as the result of a calculation Excel stores and displays the
/// error `#DIV/0`. However, this error isn't usually used outside of a
/// formula result and it isn't stored as a number.
///
/// In order to deal with [`f64::INFINITY`] in a reasonable way
/// `rust_xlsxwriter` writes it as the string "INF". The
/// `set_infinite_string()` method allows you to override this default
/// value.
///
/// See the example for [`Worksheet::set_nan_string()`] above.
///
/// # Parameters
///
/// - `inf`: The string to use for `Inf` values.
///
pub fn set_infinity_string(&mut self, inf: impl Into<String>) -> &mut Worksheet {
self.infinity = inf.into();
self
}

/// Set the default string used for negative Infinite values.
///
/// Excel doesn't support storing `-Inf` (negative infinity) values. If a
/// `-Inf` is generated as the result of a calculation Excel stores and
/// displays the error `#DIV/0`. However, this error isn't usually used
/// outside of a formula result and it isn't stored as a number.
///
///
/// In order to deal with [`f64::NEG_INFINITY`] in a reasonable way
/// `rust_xlsxwriter` writes it as the string "-INF". The
/// `set_infinite_string()` method method allows you to override this
/// default value.
///
/// See the example for [`Worksheet::set_nan_string()`] above.
///
/// # Parameters
///
/// - `inf`: The string to use for `Inf` values.
///
pub fn set_neg_infinity_string(&mut self, inf: impl Into<String>) -> &mut Worksheet {
self.neg_infinity = inf.into();
self
}

// -----------------------------------------------------------------------
// Crate level helper methods.
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -13518,12 +13633,16 @@ impl Worksheet {

// Excel doesn't have a NAN type/value so write a string instead.
if number.is_nan() {
return self.store_string(row, col, "#NUM!".to_string(), None);
return self.store_string(row, col, self.nan.clone(), format);
}

// Excel doesn't have an Infinity type/value so write a string instead.
if number.is_infinite() {
self.store_string(row, col, "#DIV/0".to_string(), None)?;
if number == f64::INFINITY {
return self.store_string(row, col, self.infinity.clone(), format);
}

return self.store_string(row, col, self.neg_infinity.clone(), format);
}

// Get the index of the format object, if any.
Expand Down
Binary file added tests/input/bootstrap73.xlsx
Binary file not shown.
Binary file added tests/input/bootstrap74.xlsx
Binary file not shown.
7 changes: 4 additions & 3 deletions tests/integration/bootstrap16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ fn create_new_xlsx_file(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.set_nan_string("#NUM!");
worksheet.set_infinity_string("#DIV/0!");
worksheet.set_neg_infinity_string("#DIV/0!");

worksheet.write_number(0, 0, f64::NAN)?;
worksheet.write_number(1, 0, f64::INFINITY)?;
worksheet.write_number(2, 0, f64::NEG_INFINITY)?;

worksheet.write_string(1, 0, "#DIV/0!")?;
worksheet.write_string(2, 0, "#DIV/0!")?;

workbook.save(filename)?;

Ok(())
Expand Down
61 changes: 61 additions & 0 deletions tests/integration/bootstrap73.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Test case that compares a file generated by rust_xlsxwriter with a file
// created by Excel.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

use crate::common;
use rust_xlsxwriter::{Workbook, XlsxError};

// Create a rust_xlsxwriter file to compare against an Excel file.
fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.write(0, 0, "NAN")?;
worksheet.write(1, 0, "INF")?;
worksheet.write(2, 0, "-INF")?;

workbook.save(filename)?;

Ok(())
}

// Test Nan and Inf mapping.
fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.write(0, 0, f64::NAN)?;
worksheet.write(1, 0, f64::INFINITY)?;
worksheet.write(2, 0, f64::NEG_INFINITY)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_bootstrap73_1() {
let test_runner = common::TestRunner::new()
.set_name("bootstrap73")
.set_function(create_new_xlsx_file_1)
.unique("1")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}

#[test]
fn test_bootstrap73_2() {
let test_runner = common::TestRunner::new()
.set_name("bootstrap73")
.set_function(create_new_xlsx_file_2)
.unique("2")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
65 changes: 65 additions & 0 deletions tests/integration/bootstrap74.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Test case that compares a file generated by rust_xlsxwriter with a file
// created by Excel.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2025, John McNamara, [email protected]

use crate::common;
use rust_xlsxwriter::{Workbook, XlsxError};

// Create a rust_xlsxwriter file to compare against an Excel file.
fn create_new_xlsx_file_1(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.write(0, 0, "Nan")?;
worksheet.write(1, 0, "Inf")?;
worksheet.write(2, 0, "NegInf")?;

workbook.save(filename)?;

Ok(())
}

// Test Nan and Inf mapping.
fn create_new_xlsx_file_2(filename: &str) -> Result<(), XlsxError> {
let mut workbook = Workbook::new();
let worksheet = workbook.add_worksheet();

worksheet.set_nan_string("Nan");
worksheet.set_infinity_string("Inf");
worksheet.set_neg_infinity_string("NegInf");

worksheet.write(0, 0, f64::NAN)?;
worksheet.write(1, 0, f64::INFINITY)?;
worksheet.write(2, 0, f64::NEG_INFINITY)?;

workbook.save(filename)?;

Ok(())
}

#[test]
fn test_bootstrap74_1() {
let test_runner = common::TestRunner::new()
.set_name("bootstrap74")
.set_function(create_new_xlsx_file_1)
.unique("1")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}

#[test]
fn test_bootstrap74_2() {
let test_runner = common::TestRunner::new()
.set_name("bootstrap74")
.set_function(create_new_xlsx_file_2)
.unique("2")
.initialize();

test_runner.assert_eq();
test_runner.cleanup();
}
2 changes: 2 additions & 0 deletions tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ mod bootstrap69;
mod bootstrap70;
mod bootstrap71;
mod bootstrap72;
mod bootstrap73;
mod bootstrap74;
mod button01;
mod button02;
mod button03;
Expand Down

0 comments on commit a6fb41f

Please sign in to comment.