mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 23:58:20 +00:00
Rewrite Mono float to string conversion; fix llDumpList2String
llDumpList2String has changed its behaviour with respect to minus zero. Now it converts -0.0 to a string without the minus sign. While testing this, we noticed several mismatches in the float to string conversions; the existing routine did not properly convert some values because as we discovered later, it is subject to double rounding; one of them is the built-in round-to-nearest-or-even while getting the first 7 significant digits, and the other is just an increment when the digit is a 5 or more, so round to nearest, ties away from zero, and is performed on the digit past the five or six visible digits that LSL shows. The new code is a tad easier to understand and more robust. A first variant of the new code is left commented out for history's sake, and will be removed in the next commit.
This commit is contained in:
parent
281ff4d96a
commit
d2e64e25a6
3 changed files with 194 additions and 54 deletions
|
@ -368,67 +368,113 @@ def v2f(v):
|
||||||
return v
|
return v
|
||||||
return Vector((ff(v[0]), ff(v[1]), ff(v[2])))
|
return Vector((ff(v[0]), ff(v[1]), ff(v[2])))
|
||||||
|
|
||||||
def f2s(val, DP=6):
|
def f2s(val, DP=6, SignedZero=True):
|
||||||
if math.isinf(val):
|
if math.isinf(val):
|
||||||
return u'Infinity' if val > 0 else u'-Infinity'
|
return u'Infinity' if val > 0 else u'-Infinity'
|
||||||
if math.isnan(val):
|
if math.isnan(val):
|
||||||
return u'NaN'
|
return u'NaN'
|
||||||
if lslcommon.LSO or val == 0.:
|
if lslcommon.LSO or SignedZero and val == 0.:
|
||||||
return u'%.*f' % (DP, val) # deals with -0.0 too
|
return u'%.*f' % (DP, val) # deals with -0.0 too
|
||||||
|
|
||||||
# Format according to Mono rules (7 decimals after the DP, found experimentally)
|
# Format according to Mono rules. Mono displays 7 significant decimal
|
||||||
s = u'%.*f' % (DP+7, val)
|
# digits, rounded with round-to-nearest-or-even.
|
||||||
|
# Decimal numbers seem to be subjected to an extra rounding:
|
||||||
|
# With 6 decimals output, 0.0000014999995 is rounded as 0.000002
|
||||||
|
# while 0.0000014999994 is rounded as 0.000001. The rounding point for the
|
||||||
|
# first rounding is the 8th significant decimal, per the above; SL applies
|
||||||
|
# a second rounding at the (DP+1)-th decimal.
|
||||||
|
|
||||||
if s[:DP+3] == u'-0.' + '0'*DP and s[DP+3] < u'5':
|
# This was an attempt to do the rounding ourselves based on the 8th digit,
|
||||||
return u'0.' + '0'*DP # underflown negatives return 0.0 except for -0.0 dealt with above
|
# but there was always one or another case that failed, depending on where
|
||||||
|
# we cut the initial formatting. Since all cases were fixed by letting the
|
||||||
|
# formatting function do the rounding, this code is now disabled and will
|
||||||
|
# be removed soon. We used either %.7e but that did rounding at the 9th
|
||||||
|
# significant digit, or %.148e which is guaranteed to not do any rounding.
|
||||||
|
# # First, find the decimal mantissa and exponent.
|
||||||
|
# m, e = (u'%.148e' % val).split(u'e')
|
||||||
|
# if m[0] == u'-':
|
||||||
|
# sgn = u'-'
|
||||||
|
# m = m[1:]
|
||||||
|
# else:
|
||||||
|
# sgn = u''
|
||||||
|
#
|
||||||
|
# # Remove the decimal point but leave it as a string; add a leading '0'
|
||||||
|
# # to catch a possible carry all the way to that digit.
|
||||||
|
# m = u'0' + m[0] + m[2:9]
|
||||||
|
# assert len(m) == 9, 'Failed with val=%.17g' % val
|
||||||
|
# # Convert the exponent to integer
|
||||||
|
# e = int(e, 10)
|
||||||
|
# # Round the mantissa according to the 8th digit
|
||||||
|
# if m[8] >= u'5':
|
||||||
|
# # Go backwards from right to left
|
||||||
|
# i = 7
|
||||||
|
# while m[i] == u'9':
|
||||||
|
# m = m[:i] + u'0' + m[i+1:]
|
||||||
|
# i = i - 1
|
||||||
|
# # Add 1 to the first digit found that was not a 9
|
||||||
|
# # (we are guaranteed to have at least one: the initial zero)
|
||||||
|
# m = m[:i] + unichr(ord(m[i]) + 1) + m[i+1:]
|
||||||
|
# # Leave the significant digits only, including the leading 0 or 1 (for
|
||||||
|
# # the second rounding)
|
||||||
|
# m = m[:8]
|
||||||
|
|
||||||
|
# First, find the decimal mantissa and exponent. The first rounding is
|
||||||
|
# derived from the conversion itself, and it applies the
|
||||||
|
# round-to-nearest-or-even rounding mode.
|
||||||
|
m, e = (u'%.6e' % val).split(u'e')
|
||||||
|
# Convert the exponent to integer
|
||||||
|
e = int(e, 10)
|
||||||
# Separate the sign
|
# Separate the sign
|
||||||
sgn = u'-' if s[0] == u'-' else u''
|
if m[0] == u'-':
|
||||||
if sgn: s = s[1:]
|
sgn = u'-'
|
||||||
|
m = m[1:]
|
||||||
# If we don't have significant digits, return zero
|
|
||||||
if s == '0.' + '0'*(DP+7):
|
|
||||||
return sgn + s[:DP+2]
|
|
||||||
|
|
||||||
# Look for position of first nonzero from the left
|
|
||||||
i = 0
|
|
||||||
while s[i] in u'0.':
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
dot = s.index(u'.')
|
|
||||||
|
|
||||||
# Find rounding point. It's either the 7th digit after the first significant one,
|
|
||||||
# or the (DP+1)-th decimal after the period, whichever comes first.
|
|
||||||
digits = 0
|
|
||||||
while digits < 7:
|
|
||||||
if i >= dot+1+DP:
|
|
||||||
break
|
|
||||||
if i == dot:
|
|
||||||
i += 1
|
|
||||||
i += 1
|
|
||||||
digits += 1
|
|
||||||
|
|
||||||
if s[i if i != dot else i+1] >= u'5':
|
|
||||||
# Rounding - increment s[:i] storing result into new_s
|
|
||||||
new_s = u''
|
|
||||||
ci = i-1 # carry index
|
|
||||||
while ci >= 0 and s[ci] == u'9':
|
|
||||||
new_s = u'0' + new_s
|
|
||||||
ci -= 1
|
|
||||||
if ci == dot:
|
|
||||||
ci -= 1 # skip over the dot
|
|
||||||
new_s = u'.' + new_s # but add it to new_s
|
|
||||||
if ci < 0:
|
|
||||||
new_s = u'1' + new_s # 9...9 -> 10...0
|
|
||||||
else:
|
|
||||||
# increment s[ci] e.g. 43999 -> 44000
|
|
||||||
new_s = s[:ci] + chr(ord(s[ci]) + 1) + new_s
|
|
||||||
else:
|
else:
|
||||||
new_s = s[:i]
|
sgn = u''
|
||||||
|
|
||||||
if i <= dot:
|
# Remove the decimal point but leave the mantissa as a string; add a
|
||||||
return sgn + new_s + u'0' * (dot - i) + u'.' + u'0' * DP
|
# leading '0' to catch a possible carry all the way to the first digit.
|
||||||
return sgn + new_s + u'0' * (dot + 1 + DP - i)
|
m = u'0' + m[0] + m[2:]
|
||||||
|
|
||||||
|
# If the first digit is at a decimal position > DP+1, the result is zero.
|
||||||
|
# Same if it lies exactly at DP+1 and the first digit is < 5.
|
||||||
|
# Otherwise, we have to check rounding at a minimum.
|
||||||
|
i = 1 if m[0] == u'0' else 0 # determine 1st significant digit
|
||||||
|
if e < -(DP+1) or e == -(DP+1) and m[i] < u'5':
|
||||||
|
return u'0.' + u'0' * DP
|
||||||
|
|
||||||
|
# Predict where DP will cut the number, so that we can apply the second
|
||||||
|
# rounding to the digit after:
|
||||||
|
i = DP + e + 2
|
||||||
|
if 0 <= i <= 7 and m[i] >= u'5':
|
||||||
|
# Need to round up; add 1 in the right place
|
||||||
|
i = i - 1
|
||||||
|
while m[i] == u'9':
|
||||||
|
m = m[:i] + u'0' + m[i+1:]
|
||||||
|
i = i - 1
|
||||||
|
# Add 1 to the first digit found that was not a 9
|
||||||
|
# (we are guaranteed to have at least one: the initial zero)
|
||||||
|
m = m[:i] + unichr(ord(m[i]) + 1) + m[i+1:]
|
||||||
|
|
||||||
|
# If first digit is 0, remove it; if not, increase the exponent because
|
||||||
|
# the leading digit has changed, so e.g. 9.9999995e4 is now 1.000000e5.
|
||||||
|
# Also, make sure that m ends up with 7 digits.
|
||||||
|
if m[0] == u'0':
|
||||||
|
m = m[1:]
|
||||||
|
else:
|
||||||
|
e = e + 1
|
||||||
|
m = m[:7]
|
||||||
|
|
||||||
|
# Now we have the final 7 digits. Complete the number using the exponent.
|
||||||
|
if e >= 6:
|
||||||
|
m = m + u'0' * (e - 6) + u'.' + u'0' * DP
|
||||||
|
elif e < 0:
|
||||||
|
m = u'0.' + u'0' * (-1 - e) + m[:]#FIXME
|
||||||
|
else:
|
||||||
|
m = m[:e+1] + u'.' + m[e+1:] + u'0' * (e - len(m) + DP + 1)
|
||||||
|
|
||||||
|
# Cut out the decimal part at DP decimals; add sign and return the result
|
||||||
|
dotpos = m.index(u'.')
|
||||||
|
return sgn + m[:dotpos + 1 + DP]
|
||||||
|
|
||||||
def vr2s(v, DP=6):
|
def vr2s(v, DP=6):
|
||||||
assert len(v) == (3 if type(v) == Vector else 4)
|
assert len(v) == (3 if type(v) == Vector else 4)
|
||||||
|
@ -560,11 +606,30 @@ def InternalTypecast(val, out, InList, f32):
|
||||||
|
|
||||||
raise ELSLInvalidType
|
raise ELSLInvalidType
|
||||||
|
|
||||||
def InternalList2Strings(val):
|
def fpz(f):
|
||||||
|
"""Like f2s but forcing positive zero."""
|
||||||
|
ret = f2s(f)
|
||||||
|
return ret if ret != u'-0.000000' else u'0.000000'
|
||||||
|
|
||||||
|
def InternalList2Strings(val, ForcePositiveZero=False):
|
||||||
"""Convert a list of misc.items to a list of strings."""
|
"""Convert a list of misc.items to a list of strings."""
|
||||||
ret = []
|
ret = []
|
||||||
for elem in val:
|
if ForcePositiveZero:
|
||||||
ret.append(InternalTypecast(elem, unicode, InList=True, f32=True))
|
for elem in val:
|
||||||
|
telem = type(elem)
|
||||||
|
if telem == unicode:
|
||||||
|
ret.append(zstr(elem))
|
||||||
|
elif telem == int:
|
||||||
|
ret.append(unicode(elem))
|
||||||
|
elif telem == Key:
|
||||||
|
ret.append(zstr(unicode(elem)))
|
||||||
|
elif telem == float:
|
||||||
|
ret.append(fpz(F32(elem)))
|
||||||
|
else: # Vector or Quaternion
|
||||||
|
ret.append(u'<' + u', '.join(fpz(F32(f)) for f in elem) + u'>')
|
||||||
|
else:
|
||||||
|
for elem in val:
|
||||||
|
ret.append(InternalTypecast(elem, unicode, InList=True, f32=True))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
good_utf8_re = re.compile(b'(?:'
|
good_utf8_re = re.compile(b'(?:'
|
||||||
|
@ -1180,7 +1245,7 @@ def llDeleteSubString(s, start, end):
|
||||||
def llDumpList2String(lst, sep):
|
def llDumpList2String(lst, sep):
|
||||||
lst = fl(lst)
|
lst = fl(lst)
|
||||||
sep = fs(sep)
|
sep = fs(sep)
|
||||||
return sep.join(InternalList2Strings(lst))
|
return sep.join(InternalList2Strings(lst, ForcePositiveZero=True))
|
||||||
|
|
||||||
def llEscapeURL(s):
|
def llEscapeURL(s):
|
||||||
s = fs(s)
|
s = fs(s)
|
||||||
|
|
38
unit_tests/expr.suite/dumplist2string.lsl
Normal file
38
unit_tests/expr.suite/dumplist2string.lsl
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Change in behaviour for llDumpList2String
|
||||||
|
[ "String cast in various ways except DumpList2String"
|
||||||
|
, (string)<-0.0, 0, 0>
|
||||||
|
, (string)[<-0.0, 0, 0>]
|
||||||
|
, (string)[-0.0]
|
||||||
|
, (string)[<4.999999418942025e-07, 0, 0>]
|
||||||
|
, (string)<4.999999418942025e-07, 0, 0>
|
||||||
|
, (string)4.999999418942025e-07
|
||||||
|
, (string)[<1.4999999393694452e-06, 0, 0>]
|
||||||
|
, (string)<1.4999999393694452e-06, 0, 0>
|
||||||
|
, (string)1.4999999393694452e-06
|
||||||
|
, (string)[<2.499999936844688e-06, 0, 0>]
|
||||||
|
, (string)<2.499999936844688e-06, 0, 0>
|
||||||
|
, (string)[2.499999936844688e-06]
|
||||||
|
, (string)2.499999936844688e-06
|
||||||
|
, (string)[<1e11,0,0>]
|
||||||
|
, (string)<1e11,0,0>
|
||||||
|
, (string)[1e11]
|
||||||
|
, (string)1e11
|
||||||
|
, llList2String([<-0.0, 0, 0>], 0)
|
||||||
|
, llList2String([<1e11, 0, 0>], 0)
|
||||||
|
, llList2CSV([<-0.0, 0, 0>])
|
||||||
|
, llList2CSV([<1e11, 0, 0>])
|
||||||
|
, "DumpList2String:"
|
||||||
|
, llDumpList2String([<-0.0, -0.0, -0.0, -0.0>], "")
|
||||||
|
, llDumpList2String([<-1e-40, -1e-40, -1e-40, -1e-40>], "")
|
||||||
|
, llDumpList2String([-0.0], "")
|
||||||
|
, llDumpList2String([<5e-7, 0, 0>], "")
|
||||||
|
, llDumpList2String([<-5e-7, 0, 0>], "")
|
||||||
|
, llDumpList2String([<4.999999418942025e-07, 0, 0>], "")
|
||||||
|
, llDumpList2String([<-4.999999418942025e-07, 0, 0>], "")
|
||||||
|
, llDumpList2String([<1.4999999393694452e-06, 0, 0>], "")
|
||||||
|
, llDumpList2String([<-1.4999999393694452e-06, 0, 0>], "")
|
||||||
|
, llDumpList2String([<2.499999936844688e-06, 0, 0>], "")
|
||||||
|
, llDumpList2String([<-2.499999936844688e-06, 0, 0>], "")
|
||||||
|
, llDumpList2String([100000000000.000000], "")
|
||||||
|
, "------"
|
||||||
|
]
|
37
unit_tests/expr.suite/dumplist2string.out
Normal file
37
unit_tests/expr.suite/dumplist2string.out
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[ "String cast in various ways except DumpList2String"
|
||||||
|
, "<-0.00000, 0.00000, 0.00000>"
|
||||||
|
, "<-0.000000, 0.000000, 0.000000>"
|
||||||
|
, "-0.000000"
|
||||||
|
, "<0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<0.00000, 0.00000, 0.00000>"
|
||||||
|
, "0.000000"
|
||||||
|
, "<0.000002, 0.000000, 0.000000>"
|
||||||
|
, "<0.00000, 0.00000, 0.00000>"
|
||||||
|
, "0.000002"
|
||||||
|
, "<0.000003, 0.000000, 0.000000>"
|
||||||
|
, "<0.00000, 0.00000, 0.00000>"
|
||||||
|
, "0.000003"
|
||||||
|
, "0.000003"
|
||||||
|
, "<100000000000.000000, 0.000000, 0.000000>"
|
||||||
|
, "<100000000000.00000, 0.00000, 0.00000>"
|
||||||
|
, "100000000000.000000"
|
||||||
|
, "100000000000.000000"
|
||||||
|
, "<-0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<100000000000.000000, 0.000000, 0.000000>"
|
||||||
|
, "<-0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<99999997952.000000, 0.000000, 0.000000>"
|
||||||
|
, "DumpList2String:"
|
||||||
|
, "<0.000000, 0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<0.000000, 0.000000, 0.000000, 0.000000>"
|
||||||
|
, "0.000000"
|
||||||
|
, "<0.000001, 0.000000, 0.000000>"
|
||||||
|
, "<-0.000001, 0.000000, 0.000000>"
|
||||||
|
, "<0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<0.000000, 0.000000, 0.000000>"
|
||||||
|
, "<0.000002, 0.000000, 0.000000>"
|
||||||
|
, "<-0.000002, 0.000000, 0.000000>"
|
||||||
|
, "<0.000003, 0.000000, 0.000000>"
|
||||||
|
, "<-0.000003, 0.000000, 0.000000>"
|
||||||
|
, "100000000000.000000"
|
||||||
|
, "------"
|
||||||
|
]
|
Loading…
Add table
Add a link
Reference in a new issue