mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2024-11-21 14:18:57 -07: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 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):
|
||||
return u'Infinity' if val > 0 else u'-Infinity'
|
||||
if math.isnan(val):
|
||||
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
|
||||
|
||||
# Format according to Mono rules (7 decimals after the DP, found experimentally)
|
||||
s = u'%.*f' % (DP+7, val)
|
||||
# Format according to Mono rules. Mono displays 7 significant decimal
|
||||
# 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':
|
||||
return u'0.' + '0'*DP # underflown negatives return 0.0 except for -0.0 dealt with above
|
||||
# This was an attempt to do the rounding ourselves based on the 8th digit,
|
||||
# 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
|
||||
sgn = u'-' if s[0] == u'-' else u''
|
||||
if sgn: s = s[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
|
||||
if m[0] == u'-':
|
||||
sgn = u'-'
|
||||
m = m[1:]
|
||||
else:
|
||||
new_s = s[:i]
|
||||
sgn = u''
|
||||
|
||||
if i <= dot:
|
||||
return sgn + new_s + u'0' * (dot - i) + u'.' + u'0' * DP
|
||||
return sgn + new_s + u'0' * (dot + 1 + DP - i)
|
||||
# Remove the decimal point but leave the mantissa as a string; add a
|
||||
# leading '0' to catch a possible carry all the way to the first digit.
|
||||
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):
|
||||
assert len(v) == (3 if type(v) == Vector else 4)
|
||||
|
@ -560,11 +606,30 @@ def InternalTypecast(val, out, InList, f32):
|
|||
|
||||
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."""
|
||||
ret = []
|
||||
for elem in val:
|
||||
ret.append(InternalTypecast(elem, unicode, InList=True, f32=True))
|
||||
if ForcePositiveZero:
|
||||
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
|
||||
|
||||
good_utf8_re = re.compile(b'(?:'
|
||||
|
@ -1180,7 +1245,7 @@ def llDeleteSubString(s, start, end):
|
|||
def llDumpList2String(lst, sep):
|
||||
lst = fl(lst)
|
||||
sep = fs(sep)
|
||||
return sep.join(InternalList2Strings(lst))
|
||||
return sep.join(InternalList2Strings(lst, ForcePositiveZero=True))
|
||||
|
||||
def llEscapeURL(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…
Reference in a new issue