Skip to content

Missing feature of Go stdlib: integers ↔︎ fractional numeric strings, without rounding errors or bignums. Arbitrary fraction precision and radix.

License

mitranim/frac

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 

Overview

Missing feature of the Go standard library: parsing and formatting int64 as a fractional numeric string, without any rounding or bignums, by using a fixed fraction size. Supports arbitrary radixes from 2 to 36.

For example:

"123"     <- frac 2, radix 10 -> 123_00
"123"     <- frac 3, radix 10 -> 123_000

"123.45"  <- frac 2, radix 10 -> 123_45
"123.45"  <- frac 3, radix 10 -> 123_450

"123.456" <- frac 2, radix 10 -> <error>
"123.456" <- frac 3, radix 10 -> 123_456

Performance on 64-bit machines is somewhat comparable to strconv and shouldn't be your bottleneck.

See API docs at https://pkg.go.dev/github.com/mitranim/frac.

Why

  • You use integers for money.
  • You deal with external APIs that use decimal strings for money.
  • You want to avoid rounding errors.
  • You don't want to deal with "big decimal" libraries.

Then frac is for you!

Usage

Basic usage:

import "github.com/mitranim/frac"

func main() {
  num, err := frac.ParseDec(`-123`, 2)
  assert(err == nil && num == -123_00)

  num, err = frac.ParseDec(`-123.00`, 2)
  assert(err == nil && num == -123_00)

  num, err = frac.ParseDec(`-123.45`, 2)
  assert(err == nil && num == -123_45)

  // Exponent exceeds allotted precision. Conversion is impossible.
  num, err = frac.ParseDec(`-123.456`, 2)
  assert(err != nil && num == 0)
}

func assert(ok bool) {if !ok {panic("unreachable")}}

Implementing a monetary type:

import "github.com/mitranim/frac"

type Cents int64

func (self *Cents) UnmarshalText(input []byte) error {
  num, err := frac.UnmarshalDec(input, 2)
  if err != nil {
    return err
  }
  *self = Cents(num)
  return nil
}

func (self Cents) MarshalText() ([]byte, error) {
  return frac.AppendDec(nil, int64(self), 2)
}

The resulting type Cents is an integer, but when decoding and encoding text, it's represented as a fractional with 2 decimal points.

Known Limitations

  • The code is too assembly-like. Kinda like the standard library.

  • No special support for unsigned integers.

  • When formatting, fractional precision is limited to 64. (Imagine allocating gigabytes of memory for 0.0...01.)

License

https://unlicense.org

Misc

I'm receptive to suggestions. If this library almost satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts

About

Missing feature of Go stdlib: integers ↔︎ fractional numeric strings, without rounding errors or bignums. Arbitrary fraction precision and radix.

Topics

Resources

License

Stars

Watchers

Forks

Languages