MISE À JOUR
Ce problème a été corrigé dans la prochaine version (5.0.0-preview4) .
Réponse originale
J'ai testé float
et double
, et c'est intéressant dans ce cas particulier, je n'ai double
eu que le problème, alors qu'il float
semble fonctionner (c'est-à-dire que 0,005 est lu sur le serveur).
L'inspection des octets de message a suggéré que 0,005 est envoyé en tant que type Float32Double
qui est un nombre à virgule flottante à simple précision IEEE 754 de 4 octets / 32 bits bien qu'il Number
soit de 64 bits à virgule flottante.
Exécutez le code suivant dans la console a confirmé ce qui précède:
msgpack5().encode(Number(0.005))
// Output
Uint8Array(5) [202, 59, 163, 215, 10]
mspack5 fournit une option pour forcer la virgule flottante 64 bits:
msgpack5({forceFloat64:true}).encode(Number(0.005))
// Output
Uint8Array(9) [203, 63, 116, 122, 225, 71, 174, 20, 123]
Cependant, l' forceFloat64
option n'est pas utilisée par signalr-protocol-msgpack .
Bien que cela explique pourquoi float
fonctionne côté serveur, mais il n'y a pas vraiment de correctif pour le moment . Attendons ce que dit Microsoft .
Solutions de contournement possibles
- Hacker les options de msgpack5? Forkez et compilez votre propre msgpack5 avec la
forceFloat64
valeur par défaut true? Je ne sais pas.
- Passer du
float
côté serveur
- Utilisation des
string
deux côtés
- Basculez vers
decimal
côté serveur et écrivez personnalisé IFormatterProvider
. decimal
n'est pas de type primitif et IFormatterProvider<decimal>
est appelé pour des propriétés de type complexes
- Fournir une méthode pour récupérer la
double
valeur de la propriété et faire l' astuce double
-> float
-> decimal
->double
- D'autres solutions irréalistes auxquelles vous pourriez penser
TL; DR
Le problème avec le client JS envoyant un seul nombre à virgule flottante au backend C # provoque un problème de virgule flottante connu:
// value = 0.00499999988824129, crazy C# :)
var value = (double)0.005f;
Pour les utilisations directes des double
méthodes in, le problème peut être résolu par une personnalisation MessagePack.IFormatterResolver
:
public class MyDoubleFormatterResolver : IFormatterResolver
{
public static MyDoubleFormatterResolver Instance = new MyDoubleFormatterResolver();
private MyDoubleFormatterResolver()
{ }
public IMessagePackFormatter<T> GetFormatter<T>()
{
return MyDoubleFormatter.Instance as IMessagePackFormatter<T>;
}
}
public sealed class MyDoubleFormatter : IMessagePackFormatter<double>, IMessagePackFormatter
{
public static readonly MyDoubleFormatter Instance = new MyDoubleFormatter();
private MyDoubleFormatter()
{
}
public int Serialize(
ref byte[] bytes,
int offset,
double value,
IFormatterResolver formatterResolver)
{
return MessagePackBinary.WriteDouble(ref bytes, offset, value);
}
public double Deserialize(
byte[] bytes,
int offset,
IFormatterResolver formatterResolver,
out int readSize)
{
double value;
if (bytes[offset] == 0xca)
{
// 4 bytes single
// cast to decimal then double will fix precision issue
value = (double)(decimal)MessagePackBinary.ReadSingle(bytes, offset, out readSize);
return value;
}
value = MessagePackBinary.ReadDouble(bytes, offset, out readSize);
return value;
}
}
Et utilisez le résolveur:
services.AddSignalR()
.AddMessagePackProtocol(options =>
{
options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
{
MyDoubleFormatterResolver.Instance,
ContractlessStandardResolver.Instance,
};
});
Le résolveur n'est pas parfait, car couler jusque- decimal
là pour double
ralentir le processus et cela pourrait être dangereux .
toutefois
Comme l'OP l'a souligné dans les commentaires, cela ne peut pas résoudre le problème si vous utilisez des types complexes ayant des double
propriétés de retour.
Une enquête plus approfondie a révélé la cause du problème dans MessagePack-CSharp:
// Type: MessagePack.MessagePackBinary
// Assembly: MessagePack, Version=1.9.0.0, Culture=neutral, PublicKeyToken=b4a0369545f0a1be
// MVID: B72E7BA0-FA95-4EB9-9083-858959938BCE
// Assembly location: ...\.nuget\packages\messagepack\1.9.11\lib\netstandard2.0\MessagePack.dll
namespace MessagePack.Decoders
{
internal sealed class Float32Double : IDoubleDecoder
{
internal static readonly IDoubleDecoder Instance = (IDoubleDecoder) new Float32Double();
private Float32Double()
{
}
public double Read(byte[] bytes, int offset, out int readSize)
{
readSize = 5;
// The problem is here
// Cast a float value to double like this causes precision loss
return (double) new Float32Bits(bytes, checked (offset + 1)).Value;
}
}
}
Le décodeur ci-dessus est utilisé pour convertir un seul float
nombre en double
:
// From MessagePackBinary class
MessagePackBinary.doubleDecoders[202] = Float32Double.Instance;
v2
Ce problème existe dans les versions v2 de MessagePack-CSharp. J'ai déposé un problème sur github , bien que le problème ne soit pas résolu .