Mojo::JSON and Math::Big...

38 views
Skip to first unread message

Tekki

unread,
Jun 30, 2014, 2:12:58 PM6/30/14
to mojol...@googlegroups.com
Mojo::JSON encodes Math::BigFloat and Math::BigInt as strings. Example:

use Mojo::JSON 'encode_json';
use Math::BigFloat;
use Math::BigInt;

my $float = Math::BigFloat->new(12.34);
my $int = Math::BigInt->new(123);

print encode_json {float => $float, int => $int}; # {"int":"123","float":"12.34"}

Wouldn't it make sense to return them as numbers instead?

Gabriel Vieira

unread,
Jun 30, 2014, 4:07:28 PM6/30/14
to mojol...@googlegroups.com
The problem is in the returned values, not in Mojo::JSON itself.
Both modules returns strings instead of numbers, to solve that, I could work with Math::BigInt methods, but I could not find any Math::BigFloat method.

Here is:

--------------8<--------------
use Mojo::JSON 'j';
use Math::BigFloat;
use Math::BigInt;

my $float = Math::BigFloat->new(12.34)->bstr;
my $int = Math::BigInt->new(123)->numify;

print j {float => $float+0, int => $int}; # {"int":123,"float":12.34}
-------------->8--------------

I don't like the +0 solution in this case, but I couldn't figure out another one.

Let's see if anyone has any other alternative or correct solution for that.

Regards,



--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.



--
Gabriel Vieira

Gabriel Vieira

unread,
Jun 30, 2014, 4:14:00 PM6/30/14
to mojol...@googlegroups.com
Sorry.. I just saw Math::BigFloat source code it also contains a numify method (that implements +0 in the return).

So.. correct code should be:

--------------8<--------------
use Mojo::JSON 'j';
use Math::BigFloat;
use Math::BigInt;

my $float = Math::BigFloat->new(12.34)->numify;
my $int = Math::BigInt->new(123)->numify;

print j {float => $float, int => $int}; # {"int":123,"float":12.34}
-------------->8--------------


Regards,

--
Gabriel Vieira

sri

unread,
Jun 30, 2014, 4:16:32 PM6/30/14
to mojol...@googlegroups.com
use Math::BigFloat;
use Math::BigInt;

Send patches for those modules and add TO_JSON methods. ;)

--
sebastian 

David Oswald

unread,
Jun 30, 2014, 4:37:57 PM6/30/14
to mojol...@googlegroups.com
What kind of number would you return them as?  Let's consider Math::BigInt:

    use Mojo::JSON 'encode_json';
    use Math::BigInt;
    
    my $int = Math::BigInt->new(50000000000000000000000');
    
    print encode_json {int => $int}; # {"int":"50000000000000000000000"}

5.0E+22 is too large of an integer to be supported by the underlying type systems, and it seems unlikely that one would prefer that it be converted to a floating point (with associated loss of precision), since in floating point math, any value that cannot be represented by exactly n/2^m loses precision.

However, you are welcome to subclass the modules to your taste, if you are ok with the lossy behavior that occurs when a BigInt that exceeds the size of an integer "Plain Old Data" type, thereby being converted to a floating point representation:

    package Math::MyBigInt;
    
    use Math::BigInt;
    our @ISA = 'Math::BigInt';
    
    sub TO_JSON {
      return shift()->numify;
    }
    
    package main;
    
    use Mojo::JSON 'encode_json';
    
    my $int = Math::MyBigInt->new('50000000000000000000000');
    
    print encode_json {int => $int}; #{"int":5e+22}

Just remember:

printf "%f", 5e+22; # 49999999999999995805696.000000






--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.



--

David Oswald
daos...@gmail.com

Tekki

unread,
Jun 30, 2014, 4:43:08 PM6/30/14
to mojol...@googlegroups.com

Am Montag, 30. Juni 2014 22:16:32 UTC+2 schrieb sri:
use Math::BigFloat;
use Math::BigInt;

Send patches for those modules and add TO_JSON methods. ;)

The most elegant solution would be to update Mojo::JSON, the easiest to modify the modules of my own application. So I'll probably subclass Math::BigFloat, add a TO_JSON method that calls ->bstr (thanks to Gabriel for the tip) and use this instead of Math::BigFloat.

David Oswald

unread,
Jun 30, 2014, 4:48:07 PM6/30/14
to mojol...@googlegroups.com
It seems to me that adding a TO_JSON method that calls ->bstr() will get you the existing behavior; that is, the modules already overload stringification such that JSON encoding will trigger stringification.  In short, "sub TO_JSON { shift()->bstr() }" should just get you existing behavior.

I think what you were wanting is numification, so your TO_JSON methods would call ->numify().  But then you will *not* be guaranteed a loss-less round trip.

Consider:

package Math::MyBigInt;

use Math::BigInt;
our @ISA = 'Math::BigInt';

sub TO_JSON {
  return shift()->numify;
}

package main;

use Mojo::JSON qw( encode_json decode_json );
use Math::BigInt;

my $int = Math::MyBigInt->new('50000000000000000000000');
my $j = encode_json {int => $int};
my $d = decode_json $j;

print "$j\n"; # {"int":5e+22}
printf "%f\n", $d->{int}; # 49999999999999995805696.000000



--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.



--

David Oswald
daos...@gmail.com

Tekki

unread,
Jun 30, 2014, 4:51:54 PM6/30/14
to mojol...@googlegroups.com

Am Montag, 30. Juni 2014 22:37:57 UTC+2 schrieb David Oswald:
What kind of number would you return them as?  Let's consider Math::BigInt:


I realize that it is not as simple as I thought at the beginning. My problem was just that libraries like AngularJS aren't very happy when they have to work with numbers like "123.45", so my idea was that if the quotes could be magically removed, all would be solved.

Tekki

unread,
Jun 30, 2014, 5:05:01 PM6/30/14
to mojol...@googlegroups.com
Am Montag, 30. Juni 2014 22:48:07 UTC+2 schrieb David Oswald:

I think what you were wanting is numification, so your TO_JSON methods would call ->numify().  But then you will *not* be guaranteed a loss-less round trip.



I will try it this way. I know that there is no guarantee, especially as JavaScript on the client side is involved. But like that I know where to search when new problems arise. Thanks for the help!

David Oswald

unread,
Jun 30, 2014, 5:17:44 PM6/30/14
to mojol...@googlegroups.com
This isn't a Mojo::JSON problem, nor a Math::BigInt or Math::BigFloat problem.  And it won't be a JavaScript problem either.  It is a problem of what happens when a floating point number is represented internally in binary format, regardless of the language.

Any JSON library on any platform is going to see {"int":5e22} as a floating point number (because 5e22 exceeds the size of any common platform's native integers).  Most platforms will treat that internally as a double.  The internal representation of 5e22 is inherently non-terminating.  You and I see 1/3rd, and intuitively know that it is represented in decimal format as 0.33333333333......... (a non-terminating sequence).  We have no problem making the mental leap that 33 cents is about 1/3rd of a dollar, even though we know in the back of our minds from elementary school that it's not exactly 1/3rd of 1.

Well, in the world of binary-representations of floating point numbers, any number that cannot be represented by m/(2^m) forms a non-terminating binary sequence, and as such, cannot be represented precisely with a finite number of bits.  This is the case in any language that stores floating point numbers in an internal binary format.

So when you send {"int":5e22} to your JavaScript (or to Python, or even back to Perl), the receiving end will translate that to an internal representation of the floating point number 5e22.  The internal binary representation, being non-terminating, is imprecise, and would be converted back to base-10 as 4.999e+22.  

Presumably you've been using Math::BigInt and Math::BigFloat because precision is of some importance in what you're working on.  When you serialize it to a plain-old-datatype, you are losing that precision, and the receiving party has no choice but to accept that loss.

If you serialize to a string, the receiving party still has an opportunity to preserve precision by converting the string to that language's concept of a BigInt or BigFloat.

In the general case, stringification is the better solution, and it might be so in your case as well; you will just have to deal with converting the string losslessly at the receiving end, if that's important to you.


--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.



--

David Oswald
daos...@gmail.com
Reply all
Reply to author
Forward
0 new messages