/*
* Copyright © 2010-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since KSFL 1.2
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.binpack;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
public class FPUtilities {
private FPUtilities() {}
public static int optimalSignWidth(int floatWidth) {
return (floatWidth > 0) ? 1 : 0;
}
public static int optimalExponentWidth(int floatWidth) {
if (floatWidth < 2) return 0;
else if (floatWidth < 4) return 1;
else if (floatWidth < 6) return 2;
else if (floatWidth < 8) return 3;
else if (floatWidth < 12) return 4;
else if (floatWidth < 20) return 5;
else if (floatWidth < 24) return 6;
else if (floatWidth < 32) return 7;
else if (floatWidth < 48) return 8;
else if (floatWidth < 56) return 9;
else if (floatWidth < 64) return 10;
else if (floatWidth < 80) return 11;
else if (floatWidth < 96) return 12;
else if (floatWidth < 112) return 13;
else if (floatWidth < 128) return 14;
else if (floatWidth < 160) return 15;
else if (floatWidth < 192) return 16;
else if (floatWidth < 224) return 17;
else if (floatWidth < 256) return 18;
else if (floatWidth < 320) return 19;
else if (floatWidth < 384) return 20;
else if (floatWidth < 448) return 21;
else if (floatWidth < 512) return 22;
else return 23;
}
public static int optimalMantissaWidth(int floatWidth) {
return (floatWidth > 2) ? (floatWidth - optimalExponentWidth(floatWidth) - 1) : 0;
}
public static int optimalBias(int exponentWidth) {
return ((1 << (exponentWidth - 1)) - 1);
}
public static BigInteger[] splitFloat(BigInteger rawFloat, int signWidth, int exponentWidth, int mantissaWidth) {
BigInteger rawSign = rawFloat.shiftRight(exponentWidth + mantissaWidth).and(BigInteger.ONE.shiftLeft(signWidth).subtract(BigInteger.ONE));
BigInteger rawExponent = rawFloat.shiftRight(mantissaWidth).and(BigInteger.ONE.shiftLeft(exponentWidth).subtract(BigInteger.ONE));
BigInteger rawMantissa = rawFloat.and(BigInteger.ONE.shiftLeft(mantissaWidth).subtract(BigInteger.ONE));
return new BigInteger[] { rawSign, rawExponent, rawMantissa };
}
public static BigInteger joinFloat(BigInteger rawSign, BigInteger rawExponent, BigInteger rawMantissa, int signWidth, int exponentWidth, int mantissaWidth) {
return (rawSign.and(BigInteger.ONE.shiftLeft(signWidth).subtract(BigInteger.ONE)).shiftLeft(exponentWidth + mantissaWidth))
.or(rawExponent.and(BigInteger.ONE.shiftLeft(exponentWidth).subtract(BigInteger.ONE)).shiftLeft(mantissaWidth))
.or(rawMantissa.and(BigInteger.ONE.shiftLeft(mantissaWidth).subtract(BigInteger.ONE)));
}
public static Number decodeFloat(BigInteger rawSign, BigInteger rawExponent, BigInteger rawMantissa, int signWidth, int exponentWidth, int mantissaWidth, int bias, MathContext mc) {
if (signWidth < 0 || signWidth > 1 || exponentWidth < 0 || mantissaWidth < 0) throw new IllegalArgumentException();
else if (rawExponent.compareTo(BigInteger.ZERO) == 0) {
// zero or subnormal
if (rawMantissa.compareTo(BigInteger.ZERO) == 0) {
// zero
// must use double, instead of BigDecimal, in order to preserve sign
boolean isNegative = (rawSign.compareTo(BigInteger.ZERO) != 0);
return isNegative ? -0.0 : 0.0;
} else {
// subnormal
boolean isNegative = (rawSign.compareTo(BigInteger.ZERO) != 0);
int negativeExponent = bias + mantissaWidth - 1;
BigDecimal mantissa = new BigDecimal(rawMantissa);
if (negativeExponent < 0) {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(-negativeExponent);
return isNegative ? mantissa.multiply(multiplier, mc).negate() : mantissa.multiply(multiplier, mc);
} else {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(negativeExponent);
return isNegative ? mantissa.divide(multiplier, mc).negate() : mantissa.divide(multiplier, mc);
}
}
} else if (rawExponent.compareTo(BigInteger.ONE.shiftLeft(exponentWidth).subtract(BigInteger.ONE)) == 0) {
// infinity or NaN
if (rawMantissa.compareTo(BigInteger.ZERO) == 0) {
// infinity
boolean isNegative = (rawSign.compareTo(BigInteger.ZERO) != 0);
return isNegative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
} else {
// NaN
boolean isNegative = (rawSign.compareTo(BigInteger.ZERO) != 0);
boolean isQuietNaN = rawMantissa.testBit(mantissaWidth-1);
BigInteger mantissa = rawMantissa.clearBit(mantissaWidth-1);
long rawDouble = 0x7FF0000000000000L;
if (isNegative) rawDouble |= 0x8000000000000000L;
if (isQuietNaN) rawDouble |= 0x0008000000000000L;
rawDouble |= (mantissa.longValue() & 0x0007FFFFFFFFFFFFL);
return Double.longBitsToDouble(rawDouble);
}
} else {
// normal
boolean isNegative = (rawSign.compareTo(BigInteger.ZERO) != 0);
int negativeExponent = bias + mantissaWidth - rawExponent.intValue();
BigDecimal mantissa = new BigDecimal(rawMantissa.setBit(mantissaWidth));
if (negativeExponent < 0) {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(-negativeExponent);
return isNegative ? mantissa.multiply(multiplier, mc).negate() : mantissa.multiply(multiplier, mc);
} else {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(negativeExponent);
return isNegative ? mantissa.divide(multiplier, mc).negate() : mantissa.divide(multiplier, mc);
}
}
}
public static BigInteger[] encodeFloat(Number v, int signWidth, int exponentWidth, int mantissaWidth, int bias, MathContext mc) {
if (signWidth < 0 || signWidth > 1 || exponentWidth < 0 || mantissaWidth < 0) throw new IllegalArgumentException();
else if (v instanceof BigDecimal) {
BigDecimal d = (BigDecimal)v;
if (d.compareTo(BigDecimal.ZERO) == 0) {
return encodeZero(false, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(d, signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else if (v instanceof BigInteger) {
BigInteger i = (BigInteger)v;
if (i.compareTo(BigInteger.ZERO) == 0) {
return encodeZero(false, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(new BigDecimal(i), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else if (v instanceof Double) {
double d = v.doubleValue();
if (Double.isNaN(d)) {
long rawDouble = Double.doubleToRawLongBits(d);
boolean isNegative = ((rawDouble & 0x8000000000000000L) != 0L);
boolean isQuietNaN = ((rawDouble & 0x0008000000000000L) != 0L);
long diagnosticCode = ((rawDouble & 0x0007FFFFFFFFFFFFL));
return encodeNaN(isNegative, isQuietNaN, diagnosticCode, signWidth, exponentWidth, mantissaWidth);
} else if (Double.isInfinite(d)) {
return encodeInfinity(d < 0.0, signWidth, exponentWidth, mantissaWidth);
} else if (d == 0.0) {
long rawDouble = Double.doubleToRawLongBits(d);
boolean isNegative = ((rawDouble & 0x8000000000000000L) != 0L);
return encodeZero(isNegative, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(BigDecimal.valueOf(d), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else if (v instanceof Float) {
float f = v.floatValue();
if (Float.isNaN(f)) {
int rawFloat = Float.floatToRawIntBits(f);
boolean isNegative = ((rawFloat & 0x80000000) != 0);
boolean isQuietNaN = ((rawFloat & 0x00400000) != 0);
int diagnosticCode = ((rawFloat & 0x003FFFFF));
return encodeNaN(isNegative, isQuietNaN, diagnosticCode, signWidth, exponentWidth, mantissaWidth);
} else if (Float.isInfinite(f)) {
return encodeInfinity(f < 0.0f, signWidth, exponentWidth, mantissaWidth);
} else if (f == 0.0f) {
int rawFloat = Float.floatToRawIntBits(f);
boolean isNegative = ((rawFloat & 0x80000000) != 0);
return encodeZero(isNegative, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(new BigDecimal(Float.toString(f)), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else if (v instanceof Long) {
long l = v.longValue();
if (l == 0l) {
return encodeZero(false, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(BigDecimal.valueOf(l), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else if (v instanceof Integer || v instanceof Short || v instanceof Byte) {
int i = v.intValue();
if (i == 0) {
return encodeZero(false, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(BigDecimal.valueOf(i), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
} else {
double d = v.doubleValue();
if (Double.isNaN(d)) {
long rawDouble = Double.doubleToRawLongBits(d);
boolean isNegative = ((rawDouble & 0x8000000000000000L) != 0L);
boolean isQuietNaN = ((rawDouble & 0x0008000000000000L) != 0L);
long diagnosticCode = ((rawDouble & 0x0007FFFFFFFFFFFFL));
return encodeNaN(isNegative, isQuietNaN, diagnosticCode, signWidth, exponentWidth, mantissaWidth);
} else if (Double.isInfinite(d)) {
return encodeInfinity(d < 0.0, signWidth, exponentWidth, mantissaWidth);
} else if (d == 0.0) {
long rawDouble = Double.doubleToRawLongBits(d);
boolean isNegative = ((rawDouble & 0x8000000000000000L) != 0L);
return encodeZero(isNegative, signWidth, exponentWidth, mantissaWidth);
} else {
return encodeFiniteNonZero(BigDecimal.valueOf(d), signWidth, exponentWidth, mantissaWidth, bias, mc);
}
}
}
private static BigInteger[] encodeNaN(boolean isNegative, boolean isQuietNaN, long diagnosticCode, int signWidth, int exponentWidth, int mantissaWidth) {
BigInteger mantissa = BigInteger.valueOf(diagnosticCode);
if (isQuietNaN) mantissa = mantissa.setBit(mantissaWidth-1);
else mantissa = mantissa.clearBit(mantissaWidth-1);
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.ONE.negate(),
mantissa
};
}
private static BigInteger[] encodeInfinity(boolean isNegative, int signWidth, int exponentWidth, int mantissaWidth) {
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.ONE.negate(),
BigInteger.ZERO
};
}
private static BigInteger[] encodeZero(boolean isNegative, int signWidth, int exponentWidth, int mantissaWidth) {
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.ZERO,
BigInteger.ZERO
};
}
private static BigInteger[] encodeFiniteNonZero(BigDecimal v, int signWidth, int exponentWidth, int mantissaWidth, int bias, MathContext mc) {
// writing floating-point numbers is HARD, especially when your big number library uses DECIMAL
boolean isNegative = (v.compareTo(BigDecimal.ZERO) < 0);
v = v.abs();
// this is the HARD part; as written, it still screws up sometimes (see evil trick)
int exponent = ilog2(v, mc) + bias;
// while loop is part of an evil trick
while (true) {
if (exponent >= ((1 << exponentWidth) - 1)) {
// overflow -> infinity
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.ONE.negate(),
BigInteger.ZERO
};
} else if (exponent <= 0) {
// underflow -> subnormal
int negativeExponent = bias + mantissaWidth - 1;
BigInteger mantissa;
if (negativeExponent < 0) {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(-negativeExponent);
mantissa = v.divide(multiplier, mc).setScale(0, mc.getRoundingMode()).toBigIntegerExact();
} else {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(negativeExponent);
mantissa = v.multiply(multiplier, mc).setScale(0, mc.getRoundingMode()).toBigIntegerExact();
}
if (mantissa.testBit(mantissaWidth)) {
// the other part of the evil trick
// this is essentially a GOTO! that's how evil this is!
exponent++;
continue;
} else {
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.ZERO,
mantissa
};
}
} else {
// normal
int negativeExponent = bias + mantissaWidth - exponent;
BigInteger mantissa;
if (negativeExponent < 0) {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(-negativeExponent);
mantissa = v.divide(multiplier, mc).setScale(0, mc.getRoundingMode()).toBigIntegerExact();
} else {
BigDecimal multiplier = BigDecimal.valueOf(2L).pow(negativeExponent);
mantissa = v.multiply(multiplier, mc).setScale(0, mc.getRoundingMode()).toBigIntegerExact();
}
if (!mantissa.testBit(mantissaWidth)) {
// the other part of the evil trick
// this is essentially a GOTO! that's how evil this is!
if (mantissa.testBit(mantissaWidth+1)) {
exponent++;
continue;
} else {
exponent--;
continue;
}
} else {
return new BigInteger[] {
(isNegative ? BigInteger.ONE.negate() : BigInteger.ZERO),
BigInteger.valueOf(exponent),
mantissa
};
}
}
}
}
private static int ilog2(BigDecimal v, MathContext mc) {
// provide a first approximation
// (v.precision()-v.scale()-1) is essentially the base-10 logarithm of v
// by multiplying this by 100000/30103 (dividing by 30103/100000, or log2(10) � 0.30103)
// we can approximate the base 2 logarithm
// naively, this would give us 0, 0, 0, 0, 3, 3, 3, 6, 6, 6, 10, 10, 10, 10, etc.
// so we add the first digit of the (base-10) mantissa for a better approximation
int approximation = (int)(
(
((long)v.precision()-(long)v.scale()-1L)*100000L
+ (long)(v.unscaledValue().toString().charAt(0)-'0')*10000L
) / 30103L
);
// now go searching for the true logarithm
approximation += 3;
while (true) {
BigDecimal power = BigDecimal.valueOf(2L).pow(Math.abs(approximation));
if (approximation < 0) power = BigDecimal.ONE.divide(power, mc);
if (power.compareTo(v) <= 0) return approximation;
approximation--;
}
// WARNING: this will go into an infinite loop with n <= 0
// since this is a private method, we assume this has been checked beforehand
}
}