If some values can't be represented as a Double, then what am I to use?

The documentation for Double states…

As Double using IEEE floating point there are some values that cannot be represented as a Double. For example, consider this code:

Var d1 As Double = 5.1
Var d2 As Double = d1 * 100
Var d3 As Double = Round(d1 * 100)

The value 5.1 cannot be stored directly in a Double (it’s actually more like 5.0999999999999996). When you multiple that actually value by 100 you end up with 509.9999999999999432 (this is d2 ) when you are really expecting a value of 510. In this case you want to use the Round method to get the value you want (d3 ).

Ok, but what if I really needed to store 5.1?!

Specifically, I need to send it as part of a JSON body.

If I divide d3 by 100 to get my decimal precision back, I get the same thing as before

{
    "d1": 5.0999999999999996447,
    "d2": 509.99999999999994316,
    "d3": 510.0,
    "d4": 5.0999999999999996447
}

Even if I convert it to a string with a fixed decimal…

Var d1 As Double = 5.1
Var d2 As Double = d1 * 100
Var d3 As Double = Round(d1 * 100)
Var d4 As Double = d3 / 100
Var s4 As String = Format(d4, "#.00")
Var d5 As Double = s4.ToDouble

It still makes up extra numbers when I convert it back again.

{
    "d1": 5.0999999999999996447,
    "d2": 509.99999999999994316,
    "d3": 510.0,
    "d4": 5.0999999999999996447,
    "s4": "5.10",
    "d5": 5.0999999999999996447
}

What am I missing here? I have to send accurate decimal numbers to a REST API that then compares them with other numbers. Asking if my number is the same as their number always fails.

I guess the answer for Xojo is to use currency. Or to round at the end of your operation to the desired precision.

2 Likes

Currency or string is your best bet.

Note that this is a universal challenge with doubles that currency doesn’t suffer from because, internally, it represents as an integer.

If you take a look at how doubles are stored in memory, you’ll understand how they can hold remarkably large numbers, but can be painfully inaccurate.

A little extra here:
Currency is best for up to 2dp

You can store up to 5 or 6 dp without loss by using integers for storage, and a method to display.
eg store your values in integers which are ‘the real value but multiplied by 100000’
0.01234 is stored as 1234

100.23 07 is stored as 10023070

All you have to do is stick a ‘.’ in the right place for display, or divide by 100000

In essence, thats what currency does using 100, to keep dollars/cents , pounds/pence intact

It is correct, but uses 10,000, currency has 4 digits to the right of the decimal point.

String, Currency, Integer or plugins provided by MBS or Bob Delaney.

Even if a limit of 2 decimal places was acceptable (which it is not), “Currency is not valid JSON type.”

How do I send a decimal value as JSON without the value being altered? The specifications of the REST server are to provide a decimal value… not a string.

I can’t believe that Xojo can’t do this without a third-party plugin. For weights and measurements in commercial and scientific fields, you must often work with decimal numbers with 4 decimals of precision. Even if storing it as an integer and then converting it every time you need to use it was an option this is enough to remove Xojo as an option for development.

JSON is all strings.
Numbers may not have quotes around them, but you arent sending 8 , 16 or whatever binary bytes to represent a double.
You are sending 15.32 as text

From what I understand ParseJSON and GenerateJSON use doubles, so there is no way to create a valid JSON like:

{“store”:{“book”:[{“category”:“reference”,“author”:“Nigel Rees”,“title”:“Sayings of the Century”,“price”:8.95},{“category”:“fiction”,“author”:“Evelyn Waugh”,“title”:“Sword of Honour”,“price”:12.99},{“category”:“fiction”,“author”:“J. R. R. Tolkien”,“title”:“The Lord of the Rings”,“isbn”:“0-395-19395-8”,“price”:22.99}],“bicycle”:{“color”:“red”,“price”:19.95}}}

Maybe someone has a way to do it?

Xojo isn’t doing anything special here, this is how doubles are stored in most, if not all, languages, and the imprecision is inherent in the standard.

Is it rejecting the value as string? Usually REST API’s don’t care.

BTW, my JSONItem_MTC class will handle this the way you want. It has a DecimalFormat property you can use.

Edit to add code and output:

var c as double = 5.10
var j as new JSONItem_MTC
j.Append c
AddToResult j.ToString

Result: [5.1]

2 Likes

Actually… it does. I’m more than a little embarrassed by this but it seems that I may have assumed that it would not but I just tested it and sending “5.10” seems to work just fine.

1 Like

It has NOT a limit of 2 decimal places.

In that case, “Double” is not a valid JSON type. You just send it withouth the quotes and jason undestand it as a NUMBER…

And I wanted to speak up for Xojo here BUT, doing some tests looks like the JSONItem is another halfbaked class after all :man_facepalming:t2:

That “Currency is not valid JSON type.” you say it is actually a XOJO error when using the currency datatype to generate the Json. this error only happens in newer versions, I use 2019r2.1 and the error is "Unrecognized Object: "

WTF, they just change the errror and did not implement the functionality to use its own data types to work with the JsonItem class :man_facepalming:t2:

Anyway, the code I tested is this:

Dim d As Double = 5.1
Dim c1 As Currency = 5.1
Dim c2 As Currency = 5.1234

Dim person As New JSONItem

person.Value ("Name") = "John Doe"
person.Value ("Direct") = 5.1
person.Value ("Double") = d
person.Value ("Currency") = c1
person.Value ("ANotherCurrency") = c2


TextArea1.Text = person.ToString

Xojo fails miserably when asigning c1, but the JSONItem_MTC class from Kem generates a perfectly valid Json with the numeric values:

{
“Name”:“John Doe”,
“Direct”:5.1,
“Double”:5.1,
“Currency”:5.1,
“ANotherCurrency”:5.1234
}

Decreasing the presentation to 8 decimals could solve many cases:

Var d As Double = 5.1
Var s As String = d.ToString(Nil, "#0.########")  // 5.1
d = 5.1234
s = d.ToString(Nil, "#0.########")  // 5.1234

For my purposes of sending accurate values via JSON, simply sending the Double as a formatted String solved the problem.

Var d1 As Double = 5.10
j.Value("d1") = Format(d1, "#.00")

Results in:

{
    "d1": "5.10"
}

To my surprise, this was accepted by the REST server as a decimal value.

They didn’t “change the error” per se, they replaced the engine within JSONItem with the library that handles ParseJSON and GenerateJSON. They intercepted those errors to raise the same type of exceptions, but with the message that comes from that library.

I agree that JSONItem should handle Currency, but likely it would treat it as a Double.

If by “accurate” you mean system rounding to 2 digits, the way you did is ok. My approach is for those needing floating, to more digits.

1 Like