to <format>: preserve round float numbers' type (#16016)

- fixes #16011

# Description
`Display` implementation for `f64` omits the decimal part for round
numbers, and by using it we did the same.
This affected:
- conversions to delimited formats: `csv`, `tsv`
- textual formats: `html`, `md`, `text`
- pretty printed `json` (`--raw` was unaffected)
- how single float values are displayed in the REPL

> [!TIP]
> This PR fixes our existing json pretty printing implementation.
> We can likely switch to using serde_json's impl using its
PrettyFormatter which allows arbitrary indent strings.

# User-Facing Changes
- Round trips through `csv`, `tsv`, and `json` preserve the type of
round floats.
- It's always clear whether a number is an integer or a float in the
REPL
  ```nushell
  4 / 2
  # => 2  # before: is this an int or a float?

  4 / 2
  # => 2.0  # after: clearly a float
  ``` 

# Tests + Formatting
Adjusted tests for the new behavior.

- 🟢 toolkit fmt
- 🟢 toolkit clippy
- 🟢 toolkit test
- 🟢 toolkit test stdlib

# After Submitting
N/A

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
Bahex
2025-06-26 23:15:19 +03:00
committed by GitHub
parent 6902bbe547
commit 5478ec44bb
32 changed files with 169 additions and 228 deletions

View File

@ -2,6 +2,7 @@ use chrono::Datelike;
use chrono_humanize::HumanTime;
use nu_engine::command_prelude::*;
use nu_protocol::{ByteStream, PipelineMetadata, format_duration, shell_error::io::IoError};
use nu_utils::ObviousFloat;
use std::io::Write;
const LINE_ENDING: &str = if cfg!(target_os = "windows") {
@ -164,7 +165,7 @@ fn local_into_string(
match value {
Value::Bool { val, .. } => val.to_string(),
Value::Int { val, .. } => val.to_string(),
Value::Float { val, .. } => val.to_string(),
Value::Float { val, .. } => ObviousFloat(val).to_string(),
Value::Filesize { val, .. } => val.to_string(),
Value::Duration { val, .. } => format_duration(val),
Value::Date { val, .. } => {

View File

@ -68,7 +68,7 @@ fn ordered_params() {
assert_eq!(
results.out,
"nimurod-20-6-1-2024-03-23 00:15:24-03:00-72400000-1000000--[104, 101, 108, 108, 111]_\
"nimurod-20-6.0-1-2024-03-23 00:15:24-03:00-72400000-1000000--[104, 101, 108, 108, 111]_\
string-int-float-int-string-int-int-nothing-binary"
);
});
@ -108,7 +108,7 @@ fn named_params() {
assert_eq!(
results.out,
"nimurod-20-6-1-2024-03-23 00:15:24-03:00-72400000-1000000--[104, 101, 108, 108, 111]_\
"nimurod-20-6.0-1-2024-03-23 00:15:24-03:00-72400000-1000000--[104, 101, 108, 108, 111]_\
string-int-float-int-string-int-int-nothing-binary"
);
});

View File

@ -80,7 +80,7 @@ fn count() {
"
));
let bit_json = r#"[ { "bit": 1, "count": 3, "quantile": 0.5, "percentage": "50.00%" }, { "bit": 0, "count": 6, "quantile": 1, "percentage": "100.00%" }]"#;
let bit_json = r#"[ { "bit": 1, "count": 3, "quantile": 0.5, "percentage": "50.00%" }, { "bit": 0, "count": 6, "quantile": 1.0, "percentage": "100.00%" }]"#;
assert_eq!(actual.out, bit_json);
}

View File

@ -38,5 +38,5 @@ fn cannot_average_infinite_range() {
#[test]
fn const_avg() {
let actual = nu!("const AVG = [1 3 5] | math avg; $AVG");
assert_eq!(actual.out, "3");
assert_eq!(actual.out, "3.0");
}

View File

@ -3,7 +3,7 @@ use nu_test_support::nu;
#[test]
fn const_log() {
let actual = nu!("const LOG = 16 | math log 2; $LOG");
assert_eq!(actual.out, "4");
assert_eq!(actual.out, "4.0");
}
#[test]

View File

@ -101,7 +101,7 @@ fn division_of_ints() {
"#
));
assert_eq!(actual.out, "2");
assert_eq!(actual.out, "2.0");
}
#[test]
@ -189,7 +189,7 @@ fn floor_division_of_floats() {
"#
));
assert_eq!(actual.out, "-2");
assert_eq!(actual.out, "-2.0");
}
#[test]

View File

@ -18,14 +18,14 @@ fn can_round_very_large_numbers_with_precision() {
fn can_round_integer_with_negative_precision() {
let actual = nu!("123 | math round --precision -1");
assert_eq!(actual.out, "120")
assert_eq!(actual.out, "120.0")
}
#[test]
fn can_round_float_with_negative_precision() {
let actual = nu!("123.3 | math round --precision -1");
assert_eq!(actual.out, "120")
assert_eq!(actual.out, "120.0")
}
#[test]

View File

@ -18,13 +18,13 @@ fn can_sqrt_irrational() {
fn can_sqrt_perfect_square() {
let actual = nu!("echo 4 | math sqrt");
assert_eq!(actual.out, "2");
assert_eq!(actual.out, "2.0");
}
#[test]
fn const_sqrt() {
let actual = nu!("const SQRT = 4 | math sqrt; $SQRT");
assert_eq!(actual.out, "2");
assert_eq!(actual.out, "2.0");
}
#[test]

View File

@ -3,7 +3,7 @@ use nu_test_support::nu;
#[test]
fn const_variance() {
let actual = nu!("const VAR = [1 2 3 4 5] | math variance; $VAR");
assert_eq!(actual.out, "2");
assert_eq!(actual.out, "2.0");
}
#[test]

View File

@ -69,7 +69,7 @@ fn mut_multiply_assign() {
fn mut_divide_assign() {
let actual = nu!("mut y = 8; $y /= 2; $y");
assert_eq!(actual.out, "4");
assert_eq!(actual.out, "4.0");
}
#[test]
@ -104,7 +104,7 @@ fn mut_path_upsert_list() {
fn mut_path_operator_assign() {
let actual = nu!("mut a = {b:1}; $a.b += 3; $a.b -= 2; $a.b *= 10; $a.b /= 4; $a.b");
assert_eq!(actual.out, "5");
assert_eq!(actual.out, "5.0");
}
#[test]

View File

@ -78,6 +78,17 @@ fn table_to_csv_text_skipping_headers_after_conversion() {
})
}
#[test]
fn table_to_csv_float_doesnt_become_int() {
let actual = nu!(pipeline(
r#"
[[a]; [1.0]] | to csv | from csv | get 0.a | describe
"#
));
assert_eq!(actual.out, "float")
}
#[test]
fn infers_types() {
Playground::setup("filter_from_csv_test_1", |dirs, sandbox| {

View File

@ -17,6 +17,17 @@ fn table_to_json_text_and_from_json_text_back_into_table() {
assert_eq!(actual.out, "markup");
}
#[test]
fn table_to_json_float_doesnt_become_int() {
let actual = nu!(pipeline(
r#"
[[a]; [1.0]] | to json | from json | get 0.a | describe
"#
));
assert_eq!(actual.out, "float")
}
#[test]
fn from_json_text_to_table() {
Playground::setup("filter_from_json_test_1", |dirs, sandbox| {

View File

@ -78,6 +78,17 @@ fn table_to_tsv_text_skipping_headers_after_conversion() {
})
}
#[test]
fn table_to_tsv_float_doesnt_become_int() {
let actual = nu!(pipeline(
r#"
[[a]; [1.0]] | to tsv | from tsv | get 0.a | describe
"#
));
assert_eq!(actual.out, "float")
}
#[test]
fn from_tsv_text_to_table() {
Playground::setup("filter_from_tsv_test_1", |dirs, sandbox| {