Skip to content

Commit

Permalink
Fix floating point number representation (#251)
Browse files Browse the repository at this point in the history
This PR change the inner representation of the decimal numbers to avoid
miss-formatting the numbers when printing them back.
  • Loading branch information
jeparlefrancais authored Jan 27, 2025
1 parent eebf917 commit 327ced2
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

* fix floating point number representation ([#251](https://github.com/seaofvoices/darklua/pull/251))
* read Luau configuration files (`.luaurc`) to get path aliases ([#246](https://github.com/seaofvoices/darklua/pull/246))
* support Luau types when bundling ([#249](https://github.com/seaofvoices/darklua/pull/249))

Expand Down
16 changes: 3 additions & 13 deletions src/generator/dense.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,8 +811,8 @@ impl LuaGenerator for DenseLuaGenerator {
use nodes::NumberExpression::*;

match number {
Decimal(number) => {
let float = number.get_raw_float();
Decimal(decimal) => {
let float = decimal.get_raw_float();
if float.is_nan() {
self.push_char('(');
self.push_char('0');
Expand All @@ -829,17 +829,7 @@ impl LuaGenerator for DenseLuaGenerator {
self.push_char('0');
self.push_char(')');
} else {
let mut result = format!("{}", float);

if let Some(exponent) = number.get_exponent() {
let exponent_char = number
.is_uppercase()
.map(|is_uppercase| if is_uppercase { 'E' } else { 'e' })
.unwrap_or('e');

result.push(exponent_char);
result.push_str(&format!("{}", exponent));
};
let result = utils::write_number(number);

self.push_str(&result);
}
Expand Down
1 change: 1 addition & 0 deletions src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ mod $mod_name {
number_100_25 => 100.25,
number_2000_05 => 2000.05,
binary_0b10101 => BinaryNumber::new(0b10101, false),
number_4_6982573308436185e159 => "4.6982573308436185e159".parse::<NumberExpression>().ok(),
));

snapshot_node!($mod_name, $generator, table, write_expression => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/generator/mod.rs
expression: generator.into_string()
---
4.6982573308436185e159
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/generator/mod.rs
expression: generator.into_string()
---
4.6982573308436185e159
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: src/generator/mod.rs
expression: generator.into_string()
---
4.6982573308436185e159
46 changes: 32 additions & 14 deletions src/generator/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! A module that contains the main [LuaGenerator](trait.LuaGenerator.html) trait
//! and its implementations.
use std::convert::TryInto;

use crate::nodes::{
Expression, FieldExpression, FunctionCall, IndexExpression, NumberExpression, Prefix,
Statement, StringSegment, TableExpression, Variable,
Expand Down Expand Up @@ -199,25 +201,41 @@ pub fn write_number(number: &NumberExpression) -> String {
match number {
NumberExpression::Decimal(number) => {
let float = number.get_raw_float();
#[allow(clippy::if_same_then_else)]
if float.is_nan() {
"(0/0)".to_owned()
} else if float.is_infinite() {
format!("({}1/0)", if float.is_sign_negative() { "-" } else { "" })
} else if let Some(exponent) = number
.get_exponent()
.map(TryInto::try_into)
.and_then(Result::ok)
{
let mantissa: f64 = float / 10.0_f64.powi(exponent);

let formatted = format!(
"{}{}{}",
mantissa,
if number.is_uppercase().unwrap_or_default() {
"E"
} else {
"e"
},
exponent
);

// verify if we did not lose any precision
if formatted.parse::<f64>() == Ok(float) {
formatted
} else if number.is_uppercase().unwrap_or_default() {
format!("{:E}", float)
} else {
format!("{:e}", float)
}
} else if float.fract() == 0.0 {
format!("{}", float)
} else {
format!(
"{}{}",
float,
number
.get_exponent()
.map(|exponent| {
let exponent_char = number
.is_uppercase()
.map(|is_uppercase| if is_uppercase { 'E' } else { 'e' })
.unwrap_or('e');
format!("{}{}", exponent_char, exponent)
})
.unwrap_or_else(|| "".to_owned())
)
format!("{:.}", float)
}
}
NumberExpression::Hex(number) => {
Expand Down
6 changes: 3 additions & 3 deletions src/nodes/expressions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ impl From<f64> for Expression {
UnaryExpression::new(UnaryOperator::Minus, Expression::from(value.abs())).into()
} else if value < 0.1 {
let exponent = value.log10().floor();
let new_value = value / 10_f64.powf(exponent);

DecimalNumber::new(new_value)
DecimalNumber::new(value)
.with_exponent(exponent as i64, true)
.into()
} else if value > 999.0 && (value / 100.0).fract() == 0.0 {
Expand All @@ -123,7 +122,7 @@ impl From<f64> for Expression {
power /= 10.0;
}

DecimalNumber::new(value / power)
DecimalNumber::new(value)
.with_exponent(exponent as i64, true)
.into()
} else {
Expand Down Expand Up @@ -346,6 +345,7 @@ mod test {
snapshot_from_expression!(
f64_0 => 0_f64,
f64_1e42 => 1e42_f64,
f64_1_2345e50 => 1.2345e50_f64,
f64_infinity => f64::INFINITY,
i64_minus_one => -1_i64,
f64_minus_zero => -0.0,
Expand Down
38 changes: 20 additions & 18 deletions src/nodes/expressions/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl DecimalNumber {
}

#[inline]
pub fn get_raw_float(&self) -> f64 {
pub(crate) fn get_raw_float(&self) -> f64 {
self.float
}

Expand All @@ -62,11 +62,7 @@ impl DecimalNumber {
}

pub fn compute_value(&self) -> f64 {
if let Some((exponent, _)) = self.exponent {
self.float * 10_f64.powf(exponent as f64)
} else {
self.float
}
self.float
}

super::impl_token_fns!(iter = [token]);
Expand Down Expand Up @@ -406,13 +402,18 @@ impl FromStr for NumberExpression {
.map(filter_underscore)
.and_then(|string| string.parse().ok())
.ok_or(Self::Err::InvalidDecimalExponent)?;
let number = value
let _number: f64 = value
.get(0..index)
.map(filter_underscore)
.and_then(|string| string.parse().ok())
.ok_or(Self::Err::InvalidDecimalNumber)?;

DecimalNumber::new(number).with_exponent(exponent, exponent_is_uppercase)
DecimalNumber::new(
filter_underscore(value)
.parse::<f64>()
.map_err(|_| Self::Err::InvalidDecimalNumber)?,
)
.with_exponent(exponent, exponent_is_uppercase)
} else {
let number = filter_underscore(value)
.parse::<f64>()
Expand Down Expand Up @@ -528,16 +529,17 @@ mod test {
parse_multiple_decimal_with_underscore_after_point("0._24") => DecimalNumber::new(0.24_f64),
parse_float_with_trailing_dot("123.") => DecimalNumber::new(123_f64),
parse_starting_with_dot(".123") => DecimalNumber::new(0.123_f64),
parse_digit_with_exponent("1e10") => DecimalNumber::new(1_f64).with_exponent(10, false),
parse_digit_with_exponent_and_underscore("1e_10") => DecimalNumber::new(1_f64).with_exponent(10, false),
parse_number_with_exponent("123e456") => DecimalNumber::new(123_f64).with_exponent(456, false),
parse_number_with_exponent_and_plus_symbol("123e+456") => DecimalNumber::new(123_f64).with_exponent(456, false),
parse_number_with_negative_exponent("123e-456") => DecimalNumber::new(123_f64).with_exponent(-456, false),
parse_number_with_upper_exponent("123E4") => DecimalNumber::new(123_f64).with_exponent(4, true),
parse_number_with_upper_negative_exponent("123E-456") => DecimalNumber::new(123_f64).with_exponent(-456, true),
parse_float_with_exponent("10.12e8") => DecimalNumber::new(10.12_f64).with_exponent(8, false),
parse_float_with_exponent_and_underscores("10_0.12_e_8") => DecimalNumber::new(100.12_f64).with_exponent(8, false),
parse_trailing_dot_with_exponent("10.e8") => DecimalNumber::new(10_f64).with_exponent(8, false),
parse_digit_with_exponent("1e10") => DecimalNumber::new(1e10_f64).with_exponent(10, false),
parse_digit_with_exponent_and_underscore("1e_10") => DecimalNumber::new(1e10_f64).with_exponent(10, false),
parse_number_with_exponent("123e101") => DecimalNumber::new(123e101_f64).with_exponent(101, false),
parse_number_with_exponent_and_plus_symbol("123e+121") => DecimalNumber::new(123e121_f64).with_exponent(121, false),
parse_number_with_negative_exponent("123e-456") => DecimalNumber::new(123e-456_f64).with_exponent(-456, false),
parse_number_with_upper_exponent("123E4") => DecimalNumber::new(123e4_f64).with_exponent(4, true),
parse_number_with_upper_negative_exponent("123E-456") => DecimalNumber::new(123e-456_f64).with_exponent(-456, true),
parse_float_with_exponent("10.12e8") => DecimalNumber::new(10.12e8_f64).with_exponent(8, false),
parse_float_with_exponent_and_underscores("10_0.12_e_8") => DecimalNumber::new(100.12e8_f64).with_exponent(8, false),
parse_float_with_exponent_2("4.6982573308436185e159") => DecimalNumber::new(4.6982573308436185e159_f64).with_exponent(159, false),
parse_trailing_dot_with_exponent("10.e8") => DecimalNumber::new(10e8_f64).with_exponent(8, false),
parse_hex_number("0x12") => HexNumber::new(18, false),
parse_hex_number_with_underscore_before_x("0_x12") => HexNumber::new(18, false),
parse_hex_number_with_underscores_around_x("0_x_12") => HexNumber::new(18, false),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: src/nodes/expressions/mod.rs
expression: result
---
Number(
Decimal(
DecimalNumber {
float: 1.2345e50,
exponent: Some(
(
46,
true,
),
),
token: None,
},
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ expression: result
Number(
Decimal(
DecimalNumber {
float: 1.0,
float: 1e42,
exponent: Some(
(
42,
Expand Down
3 changes: 1 addition & 2 deletions src/nodes/expressions/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ impl StringExpression {

match (chars.next(), chars.next_back()) {
(Some((_, first_char)), Some((_, last_char))) if first_char == last_char => {
string_utils::read_escaped_string(chars, Some(string.as_bytes().len()))
.map(Self::from_value)
string_utils::read_escaped_string(chars, Some(string.len())).map(Self::from_value)
}
(None, None) | (None, Some(_)) | (Some(_), None) => {
Err(StringError::invalid("missing quotes"))
Expand Down

0 comments on commit 327ced2

Please sign in to comment.