#2907 Java's BigDecimal vs Fantom's Decimal

Gary Sun 11 Feb

How is it that the Java version using BigDecimal is so much more verbose than the Fantom version? They both ride on the JVM. Fantom's code appears to be more comparable to the C# version. I'm not complaining, it's a big plus for Fantom! :)

Java BigDecimal:

import java.math.BigDecimal;

public class Main {
    public static void main(String[] args) {
        BigDecimal decrement = BigDecimal.valueOf(0.1);
        BigDecimal x = BigDecimal.valueOf(1.0);
        BigDecimal zero = BigDecimal.valueOf(0.0);
        while (x.compareTo(zero) >= 0) {
            System.out.println(x);
            x = x.subtract(decrement);
        }
    }
}

Fantom Decimal:

class Main {
    static Void main() {
        for (n := 1.0d; n >= 0d; n -= 0.1d) {
            echo(n)
        }
    }
}

C# Decimal:

class decimaltype
{
    static void Main(string[] args)
    {
	for (decimal x = 1m; x >= 0.0m; x -= 0.1m) {
	    Console.WriteLine(x);
	}
    }
}

SlimerDude Wed 14 Feb

Yeah, it is pretty cool.

BigDecimal in Java was always a pain to work with - I usually ended writing my own wrapper class. But now in Fantom I don't have to! :)

Gary Fri 16 Feb

Fantom is a well-thought out language. I wonder if the developer brothers are twins by chance - they would be excellent at pair programming. My twin brother is an enterprise architect. In any case, I decided to make some of the variables constant which is always good coding practice of course. I wrote it in C# first because I'm a bit more familiar with the syntax. It's using top-level statements:

// C#:
using static System.Console;

var x = 1.0m;
WriteLine(x.GetType()); // System.Decimal
const decimal decrement = 0.1m;
const decimal zero = 0.0m;
    
while (x >= zero)
{
    WriteLine(x);
    x -= decrement;
}

Here's my attempt at translating it to Fantom. It's just a touch trickier than C# since const cannot be used with local variables:

// Fantom: 
using concurrent

class DecimalConst {

    static const Decimal decrement := 0.1d
    static const Decimal zero := 0.0d

    Void main() {
        x := 1.0d
        echo(x.typeof)

        while (x >= zero) {
        echo(x)    
        x -= decrement
        }
    }
}

Does my translation attempt appear to be idiomatic Fantom? It compiles and the output is as expected:

sys::Decimal
1.0
0.9
0.8
0.7
0.6
0.5
0.4
0.3
0.2
0.1
0.0

SlimerDude Fri 16 Feb

Hi Gary,

Yes, I would say that your code is very idiomatic Fantom - you've translated it well.

If you wanted to use a field value for x, that is also possible as shown below. (But following the mantra of functional programming that all state is evil, I would advise against it!)

using concurrent

class DecimalConst {

    static const Decimal decrement := 0.1d
    static const Decimal zero      := 0.0d
                 Decimal x         := 1.0d

    Void main() {
        echo(x.typeof)
        while (x >= zero) {
            echo(x)    
            x -= decrement
        }
    }
}

Note that Fantom does also support static and instance code blocks, but I would not advocate their use.

Gary Wed 28 Feb

Hi everyone, What is the best way to format the decimal places to obtain an output like so:

1.0000000 0.9000000 0.8000000 0.7000000 0.6000000 0.5000000 0.4000000 0.3000000 0.2000000 0.1000000 0.0000000

I tried a couple different ways .. but to no avail:

echo("%.7f".format(n)) // nope

echo(n.toDecimal(7.toString)) // nope    

Henry Wed 28 Feb

Hi Gary,

You'll want to use the toLocale method on Float to format a number as a string.

Try it with the pattern "0.0000000"

Gary Wed 28 Feb

That did the trick Henry. The output is as expected. But just to be sure I've done this in idiomatic Fantom. Does this look reasonable - casting bigDecimal to a Float type before calling method toLocale?

s := n.toFloat.toLocale("0.0000000")
Env.cur.out.print(s + "\n")

Output:

1.0000000
0.9000000
0.8000000
0.7000000
0.6000000
0.5000000
0.4000000
0.3000000
0.2000000
0.1000000
0.0000000

Henry Wed 28 Feb

I'm not exactly sure what you mean about casting bigDecimal, but if you're using the Decimal Fantom class, it also has a toLocale method that accepts the same Str pattern, no need to convert it to a Float first.

Gary Wed 28 Feb

I'm sorry for the misunderstanding Henry. I guess I took you literally concerning Float when you mentioned "You'll want to use the toLocale method on Float to format a number to a string." Nice that bigDecimal affords the same method toLocale. Here's the revision and it works as expected:

s := n.toLocale("0.0000000")
Env.cur.out.printLine(s)

Gary Wed 28 Feb

Being able to specify the number of decimal places programmatically as a numeric value rather than typing out a long formatting string would be much cleaner, such as can be found in the C-based languages. Is there a way of doing this in Fantom?

D lang: writefln("%.64s", x);

Object Pascal: writeln(z:0:64);

Ouput:

1.0000000000000000000000000000000000000000000000000000000000000000
0.9000000000000000000000000000000000000000000000000000000000000000
0.8000000000000000000000000000000000000000000000000000000000000000
0.7000000000000000000000000000000000000000000000000000000000000000
0.6000000000000000000000000000000000000000000000000000000000000000
0.5000000000000000000000000000000000000000000000000000000000000000
0.4000000000000000000000000000000000000000000000000000000000000000
0.3000000000000000000000000000000000000000000000000000000000000000
0.2000000000000000000000000000000000000000000000000000000000000000
0.1000000000000000000000000000000000000000000000000000000000000000
0.0000000000000000000000000000000000000000000000000000000000000000

I was thinking of using Java's printf method since it's so similar to the C-based languages. But I'm not sure how to integrate Java syntax within Fantom code. AI was useless in this regard. :)

SlimerDude Wed 28 Feb

Hi Gary, no there is no equivalent to do the same in Fantom. The .toLocale() methods only take a basic pattern String, so the closest you'll get is:

69d.toLocale("0." + ("0" * 64))
69.0000000000000000000000000000000000000000000000000000000000000000

or

69d.toLocale("0.0".padr("0.".size + 64, '0'))
69.0000000000000000000000000000000000000000000000000000000000000000

...where you create the pattern String using conventional string concatenation.

Which, in my eyes, is much cleaner and more understandable than using some crazy bespoke syntax such as %.64s or z:0:64!

To use Java, you would have to use the JavaFFI by using the specific Java class before calling methods on it.

Gary Wed 28 Feb

Steve, that is pretty darn awesome - especially the first example you've given. Not many languages will allow using the * operator with string literals. The second example was harder for me to read until I had Claude2 explain it. So I'll put it here for others that are learning Fantom:

  • "0.0" is the starting string
  • .padr() right-pads the string
  • "0.".size gets length of "0."
  • Add 64 to pad out decimals
  • 0 pads with 0 character

So in plainer words:

  1. Start with "0.0"
  2. Calculate desired length by adding 64 to "0." size
  3. Right pad initial string to that full length with 0

I've really learned a lot from you and Henry. This code contains much of the knowledge I've garnered from you two:

using sys::Env

class Main {

    Void main() {

        println("=" * 66)
        for (n := 1.0d; n >= 0d; n -= 0.1d) {
            s := n.toLocale("0." + ("0" * 64)) 
            println(s)
        }
        println("=" * 66)
    }

    private Void println(Str s) {
        Env.cur.out.printLine(s)
    }
}

Output:

==================================================================
1.0000000000000000000000000000000000000000000000000000000000000000
0.9000000000000000000000000000000000000000000000000000000000000000
0.8000000000000000000000000000000000000000000000000000000000000000
0.7000000000000000000000000000000000000000000000000000000000000000
0.6000000000000000000000000000000000000000000000000000000000000000
0.5000000000000000000000000000000000000000000000000000000000000000
0.4000000000000000000000000000000000000000000000000000000000000000
0.3000000000000000000000000000000000000000000000000000000000000000
0.2000000000000000000000000000000000000000000000000000000000000000
0.1000000000000000000000000000000000000000000000000000000000000000
0.0000000000000000000000000000000000000000000000000000000000000000
==================================================================

SlimerDude Thu 29 Feb

Hi Gary,

Just to mention that using sys pod is implied in every Fantom source file, just as import java.lang.* is implied in Java source files.

Also echo() is a static method on Obj that prints to std-out, so you could reduce your example to:

class Main {
    Void main() {
        echo("=" * 66)
        for (n := 1.0d; n >= 0d; n -= 0.1d) {
            s := n.toLocale("0." + ("0" * 64)) 
            echo(s)
        }
        echo("=" * 66)
    }
}

With regards to the expression:

"0.0".padr("0.".size + 64, '0')

it could be simplified to:

"0.0".padr(2 + 64, '0')

But whereas it may be obvious that 64 is the number of wanted decimal places, the number 2 then becomes a Special Number; which I try to avoid. Hence the "0.".size - so I know the why and what I'm adding to 64.

pcl Thu 29 Feb

Hi Gary,

You asked about a "format string" style of method for outputting numbers: I'm still testing this out, but your question inspired me to write up and post my own implementation of such a "format string" printer. Using it, you can write @SlimerDude's example like this, to show the numbers with 64-digit precision:

using pclTextBox

class Main {
  Void main() 
  {
    echo("=" * 66)
    for (n := 1.0d; n >= 0d; n -= 0.1d) 
    {
      Format.show("~,64F\n", [n]) // call to library "show" method
    }
    echo("=" * 66)
  }
}

The method is a (Scheme-inspired) show method: see the documentation at https://peterlane.codeberg.page/fantom/pclTextBox/Format.html#show

It takes a format string and a list of arguments, and puts things into place based on the format string. There's only two interesting things:

  • numbers, which you can format based on overall width and precision, and
  • anything else, which is converted to a string but you can specify the overall width.

This is all merely a wrapper to "pad" and "toLocale", but I find the short-hand convenient for tabular output.

Link to the library is: https://peterlane.codeberg.page/fantom/#Fantom-Pods-pclTextBox

Gary Fri 1 Mar

Thank you Steve for clarifying the fundamentals of the Fantom language as well as providing tips and tricks that I cannot find simply by Googling or asking AI.

And Professor Peter C. Lane, I'm honored that you've been inspired to showcase your own implementation of a format string printer. We are really tidying up the original code with some unique and deft formatting approaches. I can't wait to try the Fantom pod you developed, pclTextBox.

Collaborating with you all is paying dividends in my journey to learning the Fantom language. :)

Gary Wed 24 Apr

Well, I finally did it! I implemented your pclTextBox in Fantom and I was finally able to REALLY print out the decimal type to 64+ decimal places of precision. The previous "solution" appeared we were just padding strings of zeroes ;) This is really exciting! Thank you Dr. Lane for an excellent Fantom pod!

using pclTextBox::Format 

class BigDecimal1 {
    static Void main() {
        echo("Hello World!")

        x := 1.0d
        decr := 0.1d

        while (x >= 0d) {
            Format.show("~,64F\n", [x])
            x = x - decr
        }
    }
}

Output:

Hello World!
1.0000000000000000000000000000000000000000000000000000000000000000
0.9000000000000000000000000000000000000000000000000000000000000000
0.8000000000000000000000000000000000000000000000000000000000000000
0.7000000000000000000000000000000000000000000000000000000000000000
0.6000000000000000000000000000000000000000000000000000000000000000
0.5000000000000000000000000000000000000000000000000000000000000000
0.4000000000000000000000000000000000000000000000000000000000000000
0.3000000000000000000000000000000000000000000000000000000000000000
0.2000000000000000000000000000000000000000000000000000000000000000
0.1000000000000000000000000000000000000000000000000000000000000000
0.0000000000000000000000000000000000000000000000000000000000000000

Login or Signup to reply.