Skip to content

Commit

Permalink
Improve readability of Float::Printer::* docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Aug 31, 2018
1 parent 0d29bb1 commit 0637870
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 75 deletions.
4 changes: 2 additions & 2 deletions src/float/printer.cr
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
require "./printer/*"

# Float::Printer is based on Grisu3 algorithm described in the 2004 paper
# `Float::Printer` is based on Grisu3 algorithm described in the 2004 paper
# "Printing Floating-Point Numbers Quickly and Accurately with Integers" by
# Florian Loitsch.
module Float::Printer
extend self
BUFFER_SIZE = 128

# Converts Float *v* to a string representation and prints it onto *io*
# Converts `Float` *v* to a string representation and prints it onto *io*.
#
# It is used by `Float64#to_s` and it is probably not necessary to use
# this directly.
Expand Down
26 changes: 15 additions & 11 deletions src/float/printer/diy_fp.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# DiyFP is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2010 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -30,15 +31,18 @@
require "./ieee"

# This "Do It Yourself Floating Point" struct implements a floating-point number
# with a `UIht64` significand and an `Int32` exponent. Normalized DiyFP numbers will
# with a `UInt64` significand and an `Int32` exponent. Normalized `DiyFP` numbers will
# have the most significant bit of the significand set.
# Multiplication and Subtraction do not normalize their results.
# DiyFP is not designed to contain special Floats (NaN and Infinity).
#
# NOTE: `DiyFP` is not designed to contain special Floats (*NaN* and *Infinity*).
struct Float::Printer::DiyFP
SIGNIFICAND_SIZE = 64
# Also known as the significand

# Also known as the significand.
property frac : UInt64
# exponent

# Exponent.
property exp : Int32

def initialize(@frac, @exp)
Expand All @@ -52,26 +56,26 @@ struct Float::Printer::DiyFP
new frac.to_u64, exp
end

# Returns a new `DiyFP` caculated as self - *other*.
# Returns a new `DiyFP` caculated as `self - other`.
#
# The exponents of both numbers must be the same and the frac of self must be
# greater than the other.
# The exponents of both numbers must be the same and the `frac` of `self`
# must be greater than the *other*.
#
# This result is not normalized.
# NOTE: This result is not normalized.
def -(other : DiyFP)
self.class.new(frac - other.frac, exp)
end

MASK32 = 0xFFFFFFFF_u32

# Returns a new `DiyFP` caculated as self * *other*.
# Returns a new `DiyFP` caculated as `self * other`.
#
# Simply "emulates" a 128 bit multiplication.
# However: the resulting number only contains 64 bits. The least
# significant 64 bits are only used for rounding the most significant 64
# bits.
#
# This result is not normalized.
# NOTE: This result is not normalized.
def *(other : DiyFP)
a = frac >> 32
b = frac & MASK32
Expand Down Expand Up @@ -119,7 +123,7 @@ struct Float::Printer::DiyFP
new(frac, exp)
end

# Normalize such that the most signficiant bit of frac is set
# Normalize such that the most signficiant bit of `frac` is set.
def self.from_f_normalized(v : Float64 | Float32)
pre_normalized = from_f(v)
f = pre_normalized.frac
Expand Down
117 changes: 69 additions & 48 deletions src/float/printer/grisu3.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Grisu3 is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -34,23 +35,28 @@ require "./cached_powers"
module Float::Printer::Grisu3
extend self

# :nodoc:
#
# Adjusts the last digit of the generated number, and screens out generated
# solutions that may be inaccurate. A solution may be inaccurate if it is
# outside the safe interval, or if we cannot prove that it is closer to the
# input than a neighboring representation of the same length.
#
# Input: * buffer pointer containing the digits of too_high / 10^kappa
# * the buffer's length
# * distance_too_high_w == (too_high - w).frac * unit
# * unsafe_interval == (too_high - too_low).frac * unit
# * rest = (too_high - buffer * 10^kappa).frac * unit
# * ten_kappa = 10^kappa * unit
# * unit = the common multiplier
# Output: returns true if the buffer is guaranteed to contain the closest
# representable number to the input.
# Modifies the generated digits in the buffer to approach (round towards) w.
def round_weed(buffer_p, length, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit)
buffer = buffer_p.to_slice(128)
# Input:
# * *buffer_ptr*: buffer pointer containing the digits of `too_high / 10^kappa`
# * *length*: the buffer's length
# * *distance_too_high_w*: `(too_high - w).frac * unit`
# * *unsafe_interval*: `(too_high - too_low).frac * unit`
# * *rest*: `(too_high - buffer * 10^kappa).frac * unit`
# * *ten_kappa*: `10^kappa * unit`
# * *unit*: the common multiplier
#
# Output: returns `true` if the buffer is guaranteed to contain the closest
# representable number to the input.
#
# Modifies the generated digits in the buffer to approach (round towards) *w*.
def round_weed(buffer_ptr, length, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit)
buffer = buffer_ptr.to_slice(128)
small_distance = distance_too_high_w - unit
big_distance = distance_too_high_w + unit

Expand Down Expand Up @@ -154,43 +160,53 @@ module Float::Printer::Grisu3
return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit)
end

# Generates the digits of input number w.
# w is a floating-point number (DiyFp), consisting of a significand and an
# exponent. Its exponent is bounded by kMinimalTargetExponent and
# kMaximalTargetExponent.
# Hence -60 <= w.e() <= -32.
# :nodoc:
#
# Returns false if it fails, in which case the generated digits in the buffer
# Generates the digits of input number *w*.
#
# *w* is a floating-point number (`DiyFp`), consisting of a significand and an
# exponent. Its exponent is bounded by `kMinimalTargetExponent` and
# `kMaximalTargetExponent`. Hence:
# -60 <= w.e() <= -32
#
# Returns `false` if it fails, in which case the generated digits in the buffer
# should not be used.
#
# Preconditions:
# * low, w and high are correct up to 1 ulp (unit in the last place). That
# is, their error must be less than a unit of their last digits.
# * low.e() == w.e() == high.e()
# * low < w < high, and taking into account their error: low~ <= high~
# * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
# Postconditions: returns false if procedure fails.
# otherwise:
# * buffer is not null-terminated, but len contains the number of digits.
# * buffer contains the shortest possible decimal digit-sequence
# such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
# correct values of low and high (without their error).
# * if more than one decimal representation gives the minimal number of
# decimal digits then the one closest to W (where W is the correct value
# of w) is chosen.
# Remark: this procedure takes into account the imprecision of its input
# * *low*, *w* and *high* are correct up to 1 ulp (unit in the last place).
# That is, their error must be less than a unit of their last digits.
# * `low.e() == w.e() == high.e()`
# * `low < w < high`, and taking into account their error: `low~ <= high~`
# * `kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent`
#
# Postconditions: returns `false` if procedure fails, otherwise:
# * buffer is not null-terminated, but len contains the number of digits.
# * buffer contains the shortest possible decimal digit-sequence
# such that `LOW < buffer * 10^kappa < HIGH`, where LOW and HIGH are the
# correct values of low and high (without their error).
# * if more than one decimal representation gives the minimal number of
# decimal digits then the one closest to W (where W is the correct value
# of w) is chosen.
#
# NOTE: This procedure takes into account the imprecision of its input
# numbers. If the precision is not enough to guarantee all the postconditions
# then false is returned. This usually happens rarely (~0.5%).
# then `false` is returned. This usually happens rarely (~0.5%).
#
# Say, for the sake of example, that
# w.e() == -48, and w.f() == 0x1234567890abcdef
# w's value can be computed by w.f() * 2^w.e()
# We can obtain w's integral digits by simply shifting w.f() by -w.e().
# -> w's integral part is 0x1234
# w's fractional part is therefore 0x567890abcdef.
# Printing w's integral part is easy (simply print 0x1234 in decimal).
# Say, for the sake of example, that:
# w.e() == -48 && w.f() == 0x1234567890abcdef
#
# w's value can be computed by `w.f() * 2^w.e()`
#
# We can obtain w's integral digits by simply shifting `w.f()` by `-w.e()`.
#
# * -> w's integral part is `0x1234`
# * w's fractional part is therefore `0x567890abcdef`.
#
# Printing w's integral part is easy (simply print `0x1234` in decimal).
# In order to print its fraction we repeatedly multiply the fraction by 10 and
# get each digit. Example the first digit after the point would be computed by
# (0x567890abcdef * 10) >> 48. -> 3
# (0x567890abcdef * 10) >> 48. -> 3
#
# The whole thing becomes slightly more complicated because we want to stop
# once we have enough digits. That is, once the digits inside the buffer
# represent 'w' we can stop. Everything inside the interval low - high
Expand Down Expand Up @@ -284,17 +300,22 @@ module Float::Printer::Grisu3
end
end

# :nodoc:
#
# Provides a decimal representation of *v*.
#
# Returns a `Tuple` of `{status, decimal_exponent, length}`
# *status* will be true if it succeeds, otherwise the result cannot be
# *status* will be `true` if it succeeds, otherwise the result cannot be
# trusted.
#
# There will be *length* digits inside the buffer (not null-terminated).
# If the function returns satatus as true true then
# v == (buffer * 10^decimal_exponent).to_f
# The digits in the buffer are the shortest representation possible: no
# 0.09999999999999999 instead of 0.1. The shorter representation will even be
# chosen even if the longer one would be closer to *v*.
# If the function returns status as `true` then
# v == (buffer * 10^decimal_exponent).to_f
#
# The digits in the buffer are the shortest representation possible:
# no `0.09999999999999999` instead of `0.1`. The shorter representation will
# even be chosen even if the longer one would be closer to *v*.
#
# The last digit will be closest to the actual *v*. That is, even if several
# digits might correctly yield *v* when read again, the closest will be
# computed.
Expand Down
32 changes: 18 additions & 14 deletions src/float/printer/ieee.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# IEEE is ported from the C++ "double-conversions" library.
#
# The following is their license:
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -30,20 +31,21 @@
module Float::Printer::IEEE
extend self

EXPONENT_MASK_64 = 0x7FF0000000000000_u64
SIGNIFICAND_MASK_64 = 0x000FFFFFFFFFFFFF_u64
HIDDEN_BIT_64 = 0x0010000000000000_u64
PHYSICAL_SIGNIFICAND_SIZE_64 = 52 # Excludes the hidden bit
SIGNIFICAND_SIZE_64 = 53
EXPONENT_MASK_64 = 0x7FF0000000000000_u64
SIGNIFICAND_MASK_64 = 0x000FFFFFFFFFFFFF_u64
HIDDEN_BIT_64 = 0x0010000000000000_u64
# Excludes the hidden bit
PHYSICAL_SIGNIFICAND_SIZE_64 = 52
SIGNIFICAND_SIZE_64 = 53
EXPONENT_BIAS_64 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE_64
DENORMAL_EXPONENT_64 = -EXPONENT_BIAS_64 + 1
SIGN_MASK_64 = 0x8000000000000000_u64

EXPONENT_MASK_32 = 0x7F800000_u32
SIGNIFICAND_MASK_32 = 0x007FFFFF_u32
HIDDEN_BIT_32 = 0x00800000_u32
PHYSICAL_SIGNIFICAND_SIZE_32 = 23 # Excludes the hidden bit
SIGNIFICAND_SIZE_32 = 24
EXPONENT_MASK_32 = 0x7F800000_u32
SIGNIFICAND_MASK_32 = 0x007FFFFF_u32
HIDDEN_BIT_32 = 0x00800000_u32
# Excludes the hidden bit
PHYSICAL_SIGNIFICAND_SIZE_32 = 23
SIGNIFICAND_SIZE_32 = 24
EXPONENT_BIAS_32 = 0x7F + PHYSICAL_SIGNIFICAND_SIZE_32
DENORMAL_EXPONENT_32 = -EXPONENT_BIAS_32 + 1
SIGN_MASK_32 = 0x80000000_u32
Expand Down Expand Up @@ -89,9 +91,11 @@ module Float::Printer::IEEE
end

# Computes the two boundaries of *v*.
# The bigger boundary (m_plus) is normalized. The lower boundary has the same
# exponent as m_plus.
# Precondition: the value encoded by this Flaot must be greater than 0.
#
# The bigger boundary (*m_plus*) is normalized. The lower boundary has the same
# exponent as *m_plus*.
#
# Precondition: the value encoded by this `Float` must be greater than 0.
def normalized_boundaries(v : Float64)
w = DiyFP.from_f(v)
m_plus = DiyFP.new((w.frac << 1) + 1, w.exp - 1).normalize
Expand Down

0 comments on commit 0637870

Please sign in to comment.