Skip to content

Commit f5c1aca

Browse files
committed
Improve numeric conversion involving floating point numbers
32+ bit integers <-> floating point numbers are more precise
1 parent d4d335d commit f5c1aca

File tree

3 files changed

+63
-39
lines changed

3 files changed

+63
-39
lines changed

AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,11 @@ private static void WriteConvertMethod(IndentedTextWriter writer, CSharpPrimitiv
163163
else
164164
{
165165
Debug.Assert(to.IsUnsignedInteger);
166-
if (from.Type == typeof(Half))
166+
CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(to, from);
167+
if (conversionType != from)
167168
{
168-
writer.WriteComment("We use float because it has enough precision to convert from Half to any integer type.");
169-
writer.WriteLine($"return {ConvertMethodName(typeof(float))}<TTo>((float)value);");
169+
writer.WriteComment($"We use {conversionType.LangName} because it has enough precision to convert from {from.LangName} to {to.LangName}.");
170+
writer.WriteLine($"return {ConvertMethodName(conversionType.Type)}<TTo>(({conversionType.LangName})value);");
170171
}
171172
else
172173
{
@@ -184,12 +185,13 @@ private static void WriteConvertMethod(IndentedTextWriter writer, CSharpPrimitiv
184185
Debug.Assert(from.IsUnsignedInteger);
185186
if (to.IsFloatingPoint)
186187
{
187-
if (to.Type == typeof(Half))
188+
CSharpPrimitives.Data conversionType = CSharpPrimitives.GetDataForIntegerFloatingPointConversion(from, to);
189+
if (conversionType != to)
188190
{
189-
writer.WriteComment("There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.");
190-
writer.WriteLine($"float x = {methodName}<float>(value);");
191-
writer.WriteLine("Half converted = (Half)x;");
192-
writer.WriteLine("return Unsafe.As<Half, TTo>(ref converted);");
191+
writer.WriteComment($"There isn't enough precision to convert from {from.LangName} to {to.LangName}, so we convert to {conversionType.LangName} first.");
192+
writer.WriteLine($"{conversionType.LangName} x = {methodName}<{conversionType.LangName}>(value);");
193+
writer.WriteLine($"{to.LangName} converted = ({to.LangName})x;");
194+
writer.WriteLine($"return Unsafe.As<{to.LangName}, TTo>(ref converted);");
193195
}
194196
else
195197
{

AssetRipper.TextureDecoder.SourceGeneration.Common/CSharpPrimitives.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,18 @@ public static bool IsFloatingPoint(Type type)
6868
return type == typeof(Half) || type == typeof(float) || type == typeof(NFloat) || type == typeof(double) || type == typeof(decimal);
6969
}
7070

71+
public static bool IsInteger(Type type)
72+
{
73+
return IsUnsignedInteger(type) || IsSignedInteger(type);
74+
}
75+
7176
public static bool IsUnsignedInteger(Type type)
7277
{
7378
return type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(nuint) || type == typeof(ulong) || type == typeof(UInt128);
7479
}
7580

81+
public static bool IsSignedInteger(Type type) => IsSignedInteger(type, out _);
82+
7683
public static bool IsSignedInteger(Type type, [NotNullWhen(true)] out Type? unsignedType)
7784
{
7885
if (type == typeof(sbyte))
@@ -195,6 +202,21 @@ public static string GetLangName(Type type)
195202
}
196203
}
197204

205+
public static Data GetDataForIntegerFloatingPointConversion(Data integerType, Data floatingPointType)
206+
{
207+
if (!IsInteger(integerType.Type))
208+
{
209+
throw new ArgumentException("The provided integer type is not an integer type.", nameof(integerType));
210+
}
211+
if (!IsFloatingPoint(floatingPointType.Type))
212+
{
213+
throw new ArgumentException("The provided floating point type is not a floating point type.", nameof(floatingPointType));
214+
}
215+
216+
Data result = integerType.Size < sizeof(uint) ? Dictionary[typeof(float)] : Dictionary[typeof(double)];
217+
return floatingPointType.Size >= result.Size ? floatingPointType : result;
218+
}
219+
198220
public abstract class Data
199221
{
200222
public abstract Type Type { get; }

AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private static TTo ConvertByte<TTo>(byte value) where TTo : unmanaged
187187
}
188188
else if (typeof(TTo) == typeof(Half))
189189
{
190-
// There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.
190+
// There isn't enough precision to convert from byte to Half, so we convert to float first.
191191
float x = ConvertByte<float>(value);
192192
Half converted = (Half)x;
193193
return Unsafe.As<Half, TTo>(ref converted);
@@ -327,7 +327,7 @@ private static TTo ConvertUInt16<TTo>(ushort value) where TTo : unmanaged
327327
}
328328
else if (typeof(TTo) == typeof(Half))
329329
{
330-
// There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.
330+
// There isn't enough precision to convert from ushort to Half, so we convert to float first.
331331
float x = ConvertUInt16<float>(value);
332332
Half converted = (Half)x;
333333
return Unsafe.As<Half, TTo>(ref converted);
@@ -470,14 +470,16 @@ private static TTo ConvertUInt32<TTo>(uint value) where TTo : unmanaged
470470
}
471471
else if (typeof(TTo) == typeof(Half))
472472
{
473-
// There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.
474-
float x = ConvertUInt32<float>(value);
473+
// There isn't enough precision to convert from uint to Half, so we convert to double first.
474+
double x = ConvertUInt32<double>(value);
475475
Half converted = (Half)x;
476476
return Unsafe.As<Half, TTo>(ref converted);
477477
}
478478
else if (typeof(TTo) == typeof(float))
479479
{
480-
float converted = (float)value / (float)uint.MaxValue;
480+
// There isn't enough precision to convert from uint to float, so we convert to double first.
481+
double x = ConvertUInt32<double>(value);
482+
float converted = (float)x;
481483
return Unsafe.As<float, TTo>(ref converted);
482484
}
483485
else if (typeof(TTo) == typeof(NFloat))
@@ -640,14 +642,16 @@ private static TTo ConvertUInt64<TTo>(ulong value) where TTo : unmanaged
640642
}
641643
else if (typeof(TTo) == typeof(Half))
642644
{
643-
// There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.
644-
float x = ConvertUInt64<float>(value);
645+
// There isn't enough precision to convert from ulong to Half, so we convert to double first.
646+
double x = ConvertUInt64<double>(value);
645647
Half converted = (Half)x;
646648
return Unsafe.As<Half, TTo>(ref converted);
647649
}
648650
else if (typeof(TTo) == typeof(float))
649651
{
650-
float converted = (float)value / (float)ulong.MaxValue;
652+
// There isn't enough precision to convert from ulong to float, so we convert to double first.
653+
double x = ConvertUInt64<double>(value);
654+
float converted = (float)x;
651655
return Unsafe.As<float, TTo>(ref converted);
652656
}
653657
else if (typeof(TTo) == typeof(NFloat))
@@ -785,14 +789,16 @@ private static TTo ConvertUInt128<TTo>(UInt128 value) where TTo : unmanaged
785789
}
786790
else if (typeof(TTo) == typeof(Half))
787791
{
788-
// There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first.
789-
float x = ConvertUInt128<float>(value);
792+
// There isn't enough precision to convert from UInt128 to Half, so we convert to double first.
793+
double x = ConvertUInt128<double>(value);
790794
Half converted = (Half)x;
791795
return Unsafe.As<Half, TTo>(ref converted);
792796
}
793797
else if (typeof(TTo) == typeof(float))
794798
{
795-
float converted = (float)value / (float)UInt128.MaxValue;
799+
// There isn't enough precision to convert from UInt128 to float, so we convert to double first.
800+
double x = ConvertUInt128<double>(value);
801+
float converted = (float)x;
796802
return Unsafe.As<float, TTo>(ref converted);
797803
}
798804
else if (typeof(TTo) == typeof(NFloat))
@@ -834,7 +840,7 @@ private static TTo ConvertHalf<TTo>(Half value) where TTo : unmanaged
834840
}
835841
else if (typeof(TTo) == typeof(byte))
836842
{
837-
// We use float because it has enough precision to convert from Half to any integer type.
843+
// We use float because it has enough precision to convert from Half to byte.
838844
return ConvertSingle<TTo>((float)value);
839845
}
840846
else if (typeof(TTo) == typeof(short))
@@ -844,7 +850,7 @@ private static TTo ConvertHalf<TTo>(Half value) where TTo : unmanaged
844850
}
845851
else if (typeof(TTo) == typeof(ushort))
846852
{
847-
// We use float because it has enough precision to convert from Half to any integer type.
853+
// We use float because it has enough precision to convert from Half to ushort.
848854
return ConvertSingle<TTo>((float)value);
849855
}
850856
else if (typeof(TTo) == typeof(int))
@@ -854,8 +860,8 @@ private static TTo ConvertHalf<TTo>(Half value) where TTo : unmanaged
854860
}
855861
else if (typeof(TTo) == typeof(uint))
856862
{
857-
// We use float because it has enough precision to convert from Half to any integer type.
858-
return ConvertSingle<TTo>((float)value);
863+
// We use double because it has enough precision to convert from Half to uint.
864+
return ConvertDouble<TTo>((double)value);
859865
}
860866
else if (typeof(TTo) == typeof(nint))
861867
{
@@ -890,8 +896,8 @@ private static TTo ConvertHalf<TTo>(Half value) where TTo : unmanaged
890896
}
891897
else if (typeof(TTo) == typeof(ulong))
892898
{
893-
// We use float because it has enough precision to convert from Half to any integer type.
894-
return ConvertSingle<TTo>((float)value);
899+
// We use double because it has enough precision to convert from Half to ulong.
900+
return ConvertDouble<TTo>((double)value);
895901
}
896902
else if (typeof(TTo) == typeof(Int128))
897903
{
@@ -900,8 +906,8 @@ private static TTo ConvertHalf<TTo>(Half value) where TTo : unmanaged
900906
}
901907
else if (typeof(TTo) == typeof(UInt128))
902908
{
903-
// We use float because it has enough precision to convert from Half to any integer type.
904-
return ConvertSingle<TTo>((float)value);
909+
// We use double because it has enough precision to convert from Half to UInt128.
910+
return ConvertDouble<TTo>((double)value);
905911
}
906912
else if (typeof(TTo) == typeof(Half))
907913
{
@@ -975,10 +981,8 @@ private static TTo ConvertSingle<TTo>(float value) where TTo : unmanaged
975981
}
976982
else if (typeof(TTo) == typeof(uint))
977983
{
978-
// x must be clamped because of rounding errors.
979-
float x = value * (float)uint.MaxValue;
980-
uint converted = (float)uint.MaxValue < x ? uint.MaxValue : (x > (float)uint.MinValue ? (uint)x : uint.MinValue);
981-
return Unsafe.As<uint, TTo>(ref converted);
984+
// We use double because it has enough precision to convert from float to uint.
985+
return ConvertDouble<TTo>((double)value);
982986
}
983987
else if (typeof(TTo) == typeof(nint))
984988
{
@@ -1013,10 +1017,8 @@ private static TTo ConvertSingle<TTo>(float value) where TTo : unmanaged
10131017
}
10141018
else if (typeof(TTo) == typeof(ulong))
10151019
{
1016-
// x must be clamped because of rounding errors.
1017-
float x = value * (float)ulong.MaxValue;
1018-
ulong converted = (float)ulong.MaxValue < x ? ulong.MaxValue : (x > (float)ulong.MinValue ? (ulong)x : ulong.MinValue);
1019-
return Unsafe.As<ulong, TTo>(ref converted);
1020+
// We use double because it has enough precision to convert from float to ulong.
1021+
return ConvertDouble<TTo>((double)value);
10201022
}
10211023
else if (typeof(TTo) == typeof(Int128))
10221024
{
@@ -1025,10 +1027,8 @@ private static TTo ConvertSingle<TTo>(float value) where TTo : unmanaged
10251027
}
10261028
else if (typeof(TTo) == typeof(UInt128))
10271029
{
1028-
// x must be clamped because of rounding errors.
1029-
float x = value * (float)UInt128.MaxValue;
1030-
UInt128 converted = (float)UInt128.MaxValue < x ? UInt128.MaxValue : (x > (float)UInt128.MinValue ? (UInt128)x : UInt128.MinValue);
1031-
return Unsafe.As<UInt128, TTo>(ref converted);
1030+
// We use double because it has enough precision to convert from float to UInt128.
1031+
return ConvertDouble<TTo>((double)value);
10321032
}
10331033
else if (typeof(TTo) == typeof(Half))
10341034
{

0 commit comments

Comments
 (0)