Skip to content

Commit 19f3d39

Browse files
committed
Fix vector and quaternion tostring() by not using %.6g
Whoops, didn't realize that number referred to the number of significant digits. Use `%.6f` and manual trailing zero trimming instead.
1 parent 5dde833 commit 19f3d39

7 files changed

Lines changed: 46 additions & 11 deletions

File tree

VM/src/cjson/lua_cjson.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,9 +863,10 @@ static void json_append_number(lua_State *l, json_config_t *cfg,
863863
static void json_append_coordinate_component(lua_State *l, strbuf_t *json, float val, bool tight = false) {
864864
if (tight && val == 0.0f)
865865
return; // Omit zeros in tight mode
866-
char format_buf[64] = {};
866+
char format_buf[256] = {};
867867
// Use shared helper to ensure consistent normalization of non-finite values
868-
size_t str_len = luai_formatfloat(format_buf, sizeof(format_buf), "%.6g", val);
868+
size_t str_len = luai_formatfloat(format_buf, sizeof(format_buf), "%.6f", val);
869+
str_len = luai_trimfloat(format_buf, str_len);
869870
strbuf_append_mem(json, format_buf, str_len);
870871
}
871872

VM/src/llsl.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,19 +1029,23 @@ static int lsl_tostring_quat(lua_State *L)
10291029
if (a == nullptr)
10301030
luaL_typeerror(L, 1, "quaternion");
10311031

1032-
char buf[128] = {};
1032+
char buf[256] = {};
10331033
char* p = buf;
10341034
*p++ = '<';
10351035

1036-
const char* format = LUAU_IS_LSL_VM(L) ? "%5.5f" : "%.6g";
1036+
bool is_lsl = LUAU_IS_LSL_VM(L);
1037+
const char* format = is_lsl ? "%5.5f" : "%.6f";
10371038
for (int i = 0; i < 4; i++)
10381039
{
10391040
if (i > 0)
10401041
{
10411042
*p++ = ',';
10421043
*p++ = ' ';
10431044
}
1044-
p += luai_formatfloat(p, buf + sizeof(buf) - p, format, a[i]);
1045+
int n = luai_formatfloat(p, buf + sizeof(buf) - p, format, a[i]);
1046+
if (!is_lsl)
1047+
n = luai_trimfloat(p, n);
1048+
p += n;
10451049
}
10461050

10471051
*p++ = '>';

VM/src/lnumprint.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,30 @@ char* luai_num2str(char* buf, double n)
370370
}
371371
}
372372

373+
// ServerLua: Strip trailing zeros and bare decimal point from formatted float
374+
int luai_trimfloat(char* buf, int len)
375+
{
376+
if (len <= 0)
377+
return len;
378+
379+
// If there is a decimal within buf and buf + len
380+
if (memchr(buf, '.', len))
381+
{
382+
// Trim trailing zeros
383+
while (len > 0 && buf[len - 1] == '0')
384+
len--;
385+
// If we're left with nothing after the decimal then trim the decimal
386+
if (len > 0 && buf[len - 1] == '.')
387+
len--;
388+
}
389+
return len;
390+
}
391+
373392
// ServerLua: Format a float with normalized non-finite values for LSL semantics
374393
int luai_formatfloat(char* buf, size_t bufsize, const char* format, float value)
375394
{
376-
LUAU_ASSERT(bufsize >= 20);
395+
// We need enough space to fit the max magnitude float with 6 decimals
396+
LUAU_ASSERT(bufsize >= 60);
377397

378398
if (isnan(value))
379399
{

VM/src/lnumutils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,6 @@ LUAI_FUNC char* luai_num2str(char* buf, double n);
123123
// This ensures consistent LSL semantics for vector/quaternion toString and JSON serialization.
124124
// Requires bufsize >= 20 and pre-zeroed buffer.
125125
LUAI_FUNC int luai_formatfloat(char* buf, size_t bufsize, const char* format, float value);
126+
LUAI_FUNC int luai_trimfloat(char* buf, int len);
126127

127128
#define luai_str2num(s, p) strtod((s), (p))

VM/src/lveclib.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,19 +355,23 @@ static int vector_tostring(lua_State *L)
355355
{
356356
auto* a = luaL_checkvector(L, 1);
357357

358-
char buf[128] = {};
358+
char buf[256] = {};
359359
char* p = buf;
360360
*p++ = '<';
361361

362-
const char* format = LUAU_IS_LSL_VM(L) ? "%5.5f" : "%.6g";
362+
bool is_lsl = LUAU_IS_LSL_VM(L);
363+
const char* format = is_lsl ? "%5.5f" : "%.6f";
363364
for (int i = 0; i < 3; i++)
364365
{
365366
if (i > 0)
366367
{
367368
*p++ = ',';
368369
*p++ = ' ';
369370
}
370-
p += luai_formatfloat(p, buf + sizeof(buf) - p, format, a[i]);
371+
int n = luai_formatfloat(p, buf + sizeof(buf) - p, format, a[i]);
372+
if (!is_lsl)
373+
n = luai_trimfloat(p, n);
374+
p += n;
371375
}
372376

373377
*p++ = '>';

tests/conformance/lljson.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ assert(lljson.encode({foo="bar"}) == '{"foo":"bar"}')
2222
assert(lljson.encode({foo=nil}) == '{}')
2323
-- But we can represent it explicitly with `lljson.null`
2424
assert(lljson.encode({foo=lljson.null}) == '{"foo":null}')
25-
assert(lljson.encode(vector(1, 2.5, 22.0 / 7.0)) == '"<1,2.5,3.14286>"')
26-
assert(lljson.encode(quaternion(1, 2.5, 22.0 / 7.0, 4)) == '"<1,2.5,3.14286,4>"')
25+
assert(lljson.encode(vector(1, 2.5, 22.0 / 7.0)) == '"<1,2.5,3.142857>"')
26+
assert(lljson.encode(quaternion(1, 2.5, 22.0 / 7.0, 4)) == '"<1,2.5,3.142857,4>"')
2727

2828
-- metatables are totally ignored
2929
local SomeMT = {}

tests/conformance/quaternion.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ assert(`{quaternion.tofwd(yaw_ninety)}` == "<0, 1, 0>")
2828
assert(`{quaternion.toleft(yaw_ninety)}` == "<-1, 0, 0>")
2929
assert(`{quaternion.toup(yaw_ninety)}` == "<0, 0, 1>")
3030

31+
-- Test that tostring doesn't lose precision for large component values
32+
assert(`{vector(123456.5, 0, 0)}` == "<123456.5, 0, 0>")
33+
assert(`{vector(1000001, 0, 0)}` == "<1000001, 0, 0>")
34+
assert(`{quaternion(123456.5, 0, 0, 1)}` == "<123456.5, 0, 0, 1>")
35+
3136
-- Check rotation module has same implementation as quaternion module
3237
for k,_ in quaternion do
3338
assert(rotation[k] ~= nil)

0 commit comments

Comments
 (0)