If your C# decimal has a large number of significant figures (approximately 18 or more) then they can be truncated when sent to Salesforce via the Partner API in a SOAP message.
Oddly, the same is not true of the SOAP representation Salesforce returns for values that has been set via Apex. You can receive back significantly more digits than will fit in a IEEE 754 double-precision binary floating-point.
What can be done about all this from the .NET side? As a starting point, I'd suggest pulling the applicable Scale and Precision from the Meta Data and rounding the value while you still have the decimal representation.
My understanding is that there are a few issues here:
- As a double is a binary floating-point number it isn't possible to accurately represent certain values, such as
0.1. What you really have is an approximation that is very close the actual number you want.
- If you perform arithmetic operations involving doubles the representation issues can produce unexpected results. E.g.
3.65d+0.05d != 3.7d. This is a particular problem after several operations as the errors accumulate. See Round-off error
- Due to (1) and (2) you shouldn't use a
double for numbers that rely of exact representations, such as currency values.
- A .NET decimal is a 102 bit number (128 bits of storage) that uses base 10 rather than base 2. The additional precision and base 10 representation make it more suitable for currency values.
- The Salesforce web services transports
decimal fields as IEEE 64-bit floating-point numbers in the SOAP message.
So while you have a C# decimal in .NET and a Salesforce decimal for the field it will be converted to a double representation in the SOAP message.
Point 2 shouldn't be an issue as hopefully you aren't performing any arithmetic on these double values, just using them to transport the value before converting them back to the decimal representation.
That just leaves the question of if the .NET decimal ► Soap double ► Salesforce decimal introduces any issues with the precision.
As a starting point, I'd suggest running a series of tests to gather some empirical data showing a conversion error. Try round tripping a few potentially problematic values.
E.g. I tried creating two custom fields on Account, one Currency(8,2) called TestCurrency and another Number(8,2) called TestNumber. I also used the existing AnnualRevenue Currency(18,0) field.
I set the value for all three fields to the explicit string "3699.9999999999995" via the partner WSDL and queried them. They all came back with the same value. So a double has no problems with this value.
When viewed via the Salesforce UI:
- AnnualRevenue appears as "$3,700",
- TestCurrency as "$3,700.00" and
- TestNumber as "3,700.00".
When queried in apex they all have the value "3699.9999999999995"
So, while the Salesforce UI respects the scale and precision settings, the underlying data structure does not. I've seen this before with the Data Exports. You will see values that couldn't possibly have been entered via the Salesforce UI with the current scale and precision settings. They might have existed before a change to the field settings, been imported via the API or set via Apex after a calculation.
Note that, at least with the Partner WSDL, you need to convert the doubles to strings to assign to the sObject Any XmlElement. While the soapType might be xsddouble you can also use the meta data to figure out if the type is currency and also get the precision and scale that Salesforce is using. .NET may perform some rounding with converting a double to a string. E.g. The double 3699.9999999999995 will be "3700" if you call ToString() with no arguments.
One area where you will definitely lose precision is if the .NET decimal has a mantissa ( floating-point number significand) that exceeds the number of digits a double can represent. Assuming 28 digits for the decimal mantissa (there are a few cases where you can have 29) we could have a number like "1.123456789012345678901234567" as a perfectly valid C# decimal.
If you try and assign that to any Salesforce decimal field via the SOAP Partner API it will be truncated to "1.1234567890123457" as it passes through the SOAP message. The field will appear in Apex with the truncated value and via subsequent partner API SOQL queries.
However, if you were to do the assignment via anonymous apex you don't lose the precision.
Account accToUpdate = new Account(Id='0017000000LeV0S');
accToUpdate.AnnualRevenue = Decimal.valueOf('1.123456789012345678901234567');
Account acc = [Select Id, AnnualRevenue from Account where Id = :accToUpdate.Id];
Excerpts from the Log (my emphasis):
Note how this time the full decimal value was maintained in Salesforce.
Now for the really odd part. If I query this Account via the partner API the full value comes back!
When configuring the length and decimal places of a Currency field you are limited to a combined length of 18 digits. This seems like it is designed to limit the currency field values to what can be represented with a double (although I'm fairly certain their will be some problem values around double max and min values).
Error: The sum of the length and decimal places must be an integer less than or equal to 18