Skip to content

Commit 327ced2

Browse files
Fix floating point number representation (#251)
This PR change the inner representation of the decimal numbers to avoid miss-formatting the numbers when printing them back.
1 parent eebf917 commit 327ced2

12 files changed

+95
-51
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Changelog
22

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

src/generator/dense.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -811,8 +811,8 @@ impl LuaGenerator for DenseLuaGenerator {
811811
use nodes::NumberExpression::*;
812812

813813
match number {
814-
Decimal(number) => {
815-
let float = number.get_raw_float();
814+
Decimal(decimal) => {
815+
let float = decimal.get_raw_float();
816816
if float.is_nan() {
817817
self.push_char('(');
818818
self.push_char('0');
@@ -829,17 +829,7 @@ impl LuaGenerator for DenseLuaGenerator {
829829
self.push_char('0');
830830
self.push_char(')');
831831
} else {
832-
let mut result = format!("{}", float);
833-
834-
if let Some(exponent) = number.get_exponent() {
835-
let exponent_char = number
836-
.is_uppercase()
837-
.map(|is_uppercase| if is_uppercase { 'E' } else { 'e' })
838-
.unwrap_or('e');
839-
840-
result.push(exponent_char);
841-
result.push_str(&format!("{}", exponent));
842-
};
832+
let result = utils::write_number(number);
843833

844834
self.push_str(&result);
845835
}

src/generator/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ mod $mod_name {
10531053
number_100_25 => 100.25,
10541054
number_2000_05 => 2000.05,
10551055
binary_0b10101 => BinaryNumber::new(0b10101, false),
1056+
number_4_6982573308436185e159 => "4.6982573308436185e159".parse::<NumberExpression>().ok(),
10561057
));
10571058

10581059
snapshot_node!($mod_name, $generator, table, write_expression => (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: src/generator/mod.rs
3+
expression: generator.into_string()
4+
---
5+
4.6982573308436185e159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: src/generator/mod.rs
3+
expression: generator.into_string()
4+
---
5+
4.6982573308436185e159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
source: src/generator/mod.rs
3+
expression: generator.into_string()
4+
---
5+
4.6982573308436185e159

src/generator/utils.rs

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! A module that contains the main [LuaGenerator](trait.LuaGenerator.html) trait
22
//! and its implementations.
33
4+
use std::convert::TryInto;
5+
46
use crate::nodes::{
57
Expression, FieldExpression, FunctionCall, IndexExpression, NumberExpression, Prefix,
68
Statement, StringSegment, TableExpression, Variable,
@@ -199,25 +201,41 @@ pub fn write_number(number: &NumberExpression) -> String {
199201
match number {
200202
NumberExpression::Decimal(number) => {
201203
let float = number.get_raw_float();
204+
#[allow(clippy::if_same_then_else)]
202205
if float.is_nan() {
203206
"(0/0)".to_owned()
204207
} else if float.is_infinite() {
205208
format!("({}1/0)", if float.is_sign_negative() { "-" } else { "" })
209+
} else if let Some(exponent) = number
210+
.get_exponent()
211+
.map(TryInto::try_into)
212+
.and_then(Result::ok)
213+
{
214+
let mantissa: f64 = float / 10.0_f64.powi(exponent);
215+
216+
let formatted = format!(
217+
"{}{}{}",
218+
mantissa,
219+
if number.is_uppercase().unwrap_or_default() {
220+
"E"
221+
} else {
222+
"e"
223+
},
224+
exponent
225+
);
226+
227+
// verify if we did not lose any precision
228+
if formatted.parse::<f64>() == Ok(float) {
229+
formatted
230+
} else if number.is_uppercase().unwrap_or_default() {
231+
format!("{:E}", float)
232+
} else {
233+
format!("{:e}", float)
234+
}
235+
} else if float.fract() == 0.0 {
236+
format!("{}", float)
206237
} else {
207-
format!(
208-
"{}{}",
209-
float,
210-
number
211-
.get_exponent()
212-
.map(|exponent| {
213-
let exponent_char = number
214-
.is_uppercase()
215-
.map(|is_uppercase| if is_uppercase { 'E' } else { 'e' })
216-
.unwrap_or('e');
217-
format!("{}{}", exponent_char, exponent)
218-
})
219-
.unwrap_or_else(|| "".to_owned())
220-
)
238+
format!("{:.}", float)
221239
}
222240
}
223241
NumberExpression::Hex(number) => {

src/nodes/expressions/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,8 @@ impl From<f64> for Expression {
109109
UnaryExpression::new(UnaryOperator::Minus, Expression::from(value.abs())).into()
110110
} else if value < 0.1 {
111111
let exponent = value.log10().floor();
112-
let new_value = value / 10_f64.powf(exponent);
113112

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

126-
DecimalNumber::new(value / power)
125+
DecimalNumber::new(value)
127126
.with_exponent(exponent as i64, true)
128127
.into()
129128
} else {
@@ -346,6 +345,7 @@ mod test {
346345
snapshot_from_expression!(
347346
f64_0 => 0_f64,
348347
f64_1e42 => 1e42_f64,
348+
f64_1_2345e50 => 1.2345e50_f64,
349349
f64_infinity => f64::INFINITY,
350350
i64_minus_one => -1_i64,
351351
f64_minus_zero => -0.0,

src/nodes/expressions/number.rs

+20-18
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ impl DecimalNumber {
4747
}
4848

4949
#[inline]
50-
pub fn get_raw_float(&self) -> f64 {
50+
pub(crate) fn get_raw_float(&self) -> f64 {
5151
self.float
5252
}
5353

@@ -62,11 +62,7 @@ impl DecimalNumber {
6262
}
6363

6464
pub fn compute_value(&self) -> f64 {
65-
if let Some((exponent, _)) = self.exponent {
66-
self.float * 10_f64.powf(exponent as f64)
67-
} else {
68-
self.float
69-
}
65+
self.float
7066
}
7167

7268
super::impl_token_fns!(iter = [token]);
@@ -406,13 +402,18 @@ impl FromStr for NumberExpression {
406402
.map(filter_underscore)
407403
.and_then(|string| string.parse().ok())
408404
.ok_or(Self::Err::InvalidDecimalExponent)?;
409-
let number = value
405+
let _number: f64 = value
410406
.get(0..index)
411407
.map(filter_underscore)
412408
.and_then(|string| string.parse().ok())
413409
.ok_or(Self::Err::InvalidDecimalNumber)?;
414410

415-
DecimalNumber::new(number).with_exponent(exponent, exponent_is_uppercase)
411+
DecimalNumber::new(
412+
filter_underscore(value)
413+
.parse::<f64>()
414+
.map_err(|_| Self::Err::InvalidDecimalNumber)?,
415+
)
416+
.with_exponent(exponent, exponent_is_uppercase)
416417
} else {
417418
let number = filter_underscore(value)
418419
.parse::<f64>()
@@ -528,16 +529,17 @@ mod test {
528529
parse_multiple_decimal_with_underscore_after_point("0._24") => DecimalNumber::new(0.24_f64),
529530
parse_float_with_trailing_dot("123.") => DecimalNumber::new(123_f64),
530531
parse_starting_with_dot(".123") => DecimalNumber::new(0.123_f64),
531-
parse_digit_with_exponent("1e10") => DecimalNumber::new(1_f64).with_exponent(10, false),
532-
parse_digit_with_exponent_and_underscore("1e_10") => DecimalNumber::new(1_f64).with_exponent(10, false),
533-
parse_number_with_exponent("123e456") => DecimalNumber::new(123_f64).with_exponent(456, false),
534-
parse_number_with_exponent_and_plus_symbol("123e+456") => DecimalNumber::new(123_f64).with_exponent(456, false),
535-
parse_number_with_negative_exponent("123e-456") => DecimalNumber::new(123_f64).with_exponent(-456, false),
536-
parse_number_with_upper_exponent("123E4") => DecimalNumber::new(123_f64).with_exponent(4, true),
537-
parse_number_with_upper_negative_exponent("123E-456") => DecimalNumber::new(123_f64).with_exponent(-456, true),
538-
parse_float_with_exponent("10.12e8") => DecimalNumber::new(10.12_f64).with_exponent(8, false),
539-
parse_float_with_exponent_and_underscores("10_0.12_e_8") => DecimalNumber::new(100.12_f64).with_exponent(8, false),
540-
parse_trailing_dot_with_exponent("10.e8") => DecimalNumber::new(10_f64).with_exponent(8, false),
532+
parse_digit_with_exponent("1e10") => DecimalNumber::new(1e10_f64).with_exponent(10, false),
533+
parse_digit_with_exponent_and_underscore("1e_10") => DecimalNumber::new(1e10_f64).with_exponent(10, false),
534+
parse_number_with_exponent("123e101") => DecimalNumber::new(123e101_f64).with_exponent(101, false),
535+
parse_number_with_exponent_and_plus_symbol("123e+121") => DecimalNumber::new(123e121_f64).with_exponent(121, false),
536+
parse_number_with_negative_exponent("123e-456") => DecimalNumber::new(123e-456_f64).with_exponent(-456, false),
537+
parse_number_with_upper_exponent("123E4") => DecimalNumber::new(123e4_f64).with_exponent(4, true),
538+
parse_number_with_upper_negative_exponent("123E-456") => DecimalNumber::new(123e-456_f64).with_exponent(-456, true),
539+
parse_float_with_exponent("10.12e8") => DecimalNumber::new(10.12e8_f64).with_exponent(8, false),
540+
parse_float_with_exponent_and_underscores("10_0.12_e_8") => DecimalNumber::new(100.12e8_f64).with_exponent(8, false),
541+
parse_float_with_exponent_2("4.6982573308436185e159") => DecimalNumber::new(4.6982573308436185e159_f64).with_exponent(159, false),
542+
parse_trailing_dot_with_exponent("10.e8") => DecimalNumber::new(10e8_f64).with_exponent(8, false),
541543
parse_hex_number("0x12") => HexNumber::new(18, false),
542544
parse_hex_number_with_underscore_before_x("0_x12") => HexNumber::new(18, false),
543545
parse_hex_number_with_underscores_around_x("0_x_12") => HexNumber::new(18, false),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: src/nodes/expressions/mod.rs
3+
expression: result
4+
---
5+
Number(
6+
Decimal(
7+
DecimalNumber {
8+
float: 1.2345e50,
9+
exponent: Some(
10+
(
11+
46,
12+
true,
13+
),
14+
),
15+
token: None,
16+
},
17+
),
18+
)

src/nodes/expressions/snapshots/darklua_core__nodes__expressions__test__expression_from_floats__f64_1e42.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: result
55
Number(
66
Decimal(
77
DecimalNumber {
8-
float: 1.0,
8+
float: 1e42,
99
exponent: Some(
1010
(
1111
42,

src/nodes/expressions/string.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ impl StringExpression {
4242

4343
match (chars.next(), chars.next_back()) {
4444
(Some((_, first_char)), Some((_, last_char))) if first_char == last_char => {
45-
string_utils::read_escaped_string(chars, Some(string.as_bytes().len()))
46-
.map(Self::from_value)
45+
string_utils::read_escaped_string(chars, Some(string.len())).map(Self::from_value)
4746
}
4847
(None, None) | (None, Some(_)) | (Some(_), None) => {
4948
Err(StringError::invalid("missing quotes"))

0 commit comments

Comments
 (0)